Browse Source

Added a harness for swtpm so that TPM tests can't interfere
with each other.

Matthew Carr 2 years ago
parent
commit
aad001927a
2 changed files with 125 additions and 32 deletions
  1. 2 2
      crates/btnode/scripts/swtpm_setup.conf
  2. 123 30
      crates/btnode/src/crypto/tpm.rs

+ 2 - 2
crates/btnode/scripts/swtpm_setup.conf

@@ -1,6 +1,6 @@
 # Program invoked for creating certificates
 create_certs_tool= /usr/bin/swtpm_localca
-create_certs_tool_config = $PWD/swtpm-localca.conf
-create_certs_tool_options = $PWD/swtpm-localca.options
+#create_certs_tool_config = $PWD/swtpm-localca.conf
+#create_certs_tool_options = $PWD/swtpm-localca.options
 # Comma-separated list (no spaces) of PCR banks to activate by default
 active_pcr_banks = sha256

+ 123 - 30
crates/btnode/src/crypto/tpm.rs

@@ -960,6 +960,13 @@ mod test {
     use tempdir::TempDir;
     use std::{
         fs::File,
+        process::{
+            Child,
+            Command,
+            ExitStatus,
+            Stdio
+        },
+        sync::atomic::{AtomicU16, Ordering},
     };
     use tss_esapi::{
         interface_types::{
@@ -974,15 +981,99 @@ mod test {
     };
     use ctor::ctor;
 
-    trait TestContextExt {
-        fn for_test() -> Result<Context>;
+    trait ExitStatusExt {
+        fn success_or_err(&self) -> Result<()>;
+    }
+
+    impl ExitStatusExt for ExitStatus {
+        fn success_or_err(&self) -> Result<()> {
+            match self.success() {
+                true => Ok(()),
+                false => Err(Error::custom("ExitCode was not successful")),
+            }
+        }
     }
 
-    impl TestContextExt for Context {
-        fn for_test() -> Result<Context> {
-            let config = TabrmdConfig::from_str("bus_type=session").conv_err()?;
+    struct SwtpmHarness {
+        dir: TempDir,
+        swtpm: Child,
+        abrmd: Child,
+        bus_name: String,
+        state_path: PathBuf,
+    }
+
+    impl SwtpmHarness {
+        fn new() -> Result<SwtpmHarness> {
+            static PORT: AtomicU16 = AtomicU16::new(21901);
+            let port = PORT.fetch_add(2, Ordering::SeqCst);
+            let ctrl_port = port + 1;
+            let dir = TempDir::new(format!("btnode.{port}").as_str()).conv_err()?;
+            let dir_path = dir.path();
+            let dir_path_display = dir_path.display();
+            let bus_name = format!("btnode.port{port}");
+            let conf_path = dir_path.join("swtpm_setup.conf");
+            let state_path = dir_path.join("state.bt");
+            let addr = "127.0.0.1";
+            std::fs::write(&conf_path,
+r#"# Program invoked for creating certificates
+create_certs_tool= /usr/bin/swtpm_localca
+# Comma-separated list (no spaces) of PCR banks to activate by default
+active_pcr_banks = sha256
+"#)?;
+            Command::new("swtpm_setup")
+                .args([
+                    "--tpm2",
+                    "--config", conf_path.to_str().unwrap(),
+                    "--tpm-state", format!("dir://{dir_path_display}").as_str(),
+                ])
+                .stdout(Stdio::null())
+                .status()?
+                .success_or_err()?;
+            let swtpm = Command::new("swtpm")
+                .args([
+                    "socket",
+                    "--tpm2",
+                    "--server", format!("type=tcp,port={port},bindaddr={addr}").as_str(),
+                    "--ctrl", format!("type=tcp,port={ctrl_port},bindaddr={addr}").as_str(),
+                    "--log", format!("file={dir_path_display}/log.txt,level=5").as_str(),
+                    "--flags", "not-need-init,startup-clear",
+                    "--tpmstate", format!("dir={dir_path_display}").as_str(),
+                ])
+                .spawn()?;
+            let abrmd = Command::new("tpm2-abrmd")
+                .args([
+                    "--session",
+                    format!("--dbus-name={bus_name}").as_str(),
+                    format!("--tcti=swtpm:host={addr},port={port}").as_str(),
+                ])
+                .spawn()?;
+            std::thread::sleep(std::time::Duration::from_millis(50));
+            Ok(SwtpmHarness { dir, swtpm, abrmd, bus_name, state_path })
+        }
+
+        fn context(&self) -> Result<Context> {
+            let config_string = format!("bus_type=session,bus_name={}", self.bus_name);
+            let config = TabrmdConfig::from_str(config_string.as_str()).conv_err()?;
             Context::new(TctiNameConf::Tabrmd(config)).conv_err()
         }
+
+        fn state_path(&self) -> &Path {
+            &self.state_path
+        }
+    }
+
+    impl Drop for SwtpmHarness {
+        fn drop(&mut self) {
+            if let Err(error) = self.abrmd.kill() {
+                error!("failed to kill tpm2-abrmd process with PID {}: {}", self.abrmd.id(), error);
+            }
+            if let Err(error) = self.abrmd.wait() {
+                error!("failed to wait for abrmd with PID {} to halt: {}", self.abrmd.id(), error);
+            }
+            if let Err(error) = self.swtpm.kill() {
+                error!("failed to kill swtpm process with PID {}: {}", self.swtpm.id(), error);
+            }
+        }
     }
 
     #[ctor]
@@ -1000,13 +1091,14 @@ mod test {
 
     #[test]
     fn create_context() {
-        let mut context = Context::for_test().unwrap();
-        context.self_test(true).unwrap();
+        let harness = SwtpmHarness::new().unwrap();
+        harness.context().unwrap().self_test(true).unwrap();
     }
 
     #[test]
     fn create_primary_key() {
-        let mut context = Context::for_test().unwrap();
+        let harness = SwtpmHarness::new().unwrap();
+        let mut context = harness.context().unwrap();
 
         let public = {
             let object_attributes = ObjectAttributes::builder()
@@ -1069,9 +1161,10 @@ mod test {
     /// Tests that a TPM Credential Store can be created when a cookie does not already exist.
     #[test]
     fn tpm_cred_store_new() -> Result<()> {
+        let harness = SwtpmHarness::new()?;
         let dir = TempDir::new("btnode").conv_err()?;
         let cookie_path = dir.path().join("cookie.bin");
-        let store = TpmCredStore::new(Context::for_test()?, &cookie_path)?;
+        let store = TpmCredStore::new(harness.context()?, &cookie_path)?;
         let cookie = File::open(&cookie_path).conv_err()?;
         let metadata = cookie.metadata().conv_err()?;
         let actual = metadata.permissions().mode();
@@ -1084,9 +1177,10 @@ mod test {
 
     #[test]
     fn gen_creds() -> Result<()> {
+        let harness = SwtpmHarness::new()?;
         let dir = TempDir::new("btnode").conv_err()?;
         let cookie_path = dir.path().join("cookie.bin");
-        let store = TpmCredStore::new(Context::for_test()?, &cookie_path)?;
+        let store = TpmCredStore::new(harness.context()?, &cookie_path)?;
         store.gen_node_creds()?;
         Ok(())
     }
@@ -1123,15 +1217,12 @@ mod test {
         assert_eq(MessageDigest::sha3_512(), Nid::sha3_512());
     }
 
-    fn state_path<P: AsRef<Path>>(dir: P) -> PathBuf {
-        dir.as_ref().join("state.bt")
-    }
-
-    fn test_store() -> Result<(TpmCredStore, TempDir)> {
-        let dir = TempDir::new("btnode").conv_err()?;
-        let state_path = state_path(dir.path());
-        let store = TpmCredStore::new(Context::for_test()?, &state_path)?;       
-        Ok((store, dir))
+    /// Returns a SwtpmHarness and a TpmCredStore that uses it. Note that the order of the entries
+    /// in the returned tuple is significant, as TpmCredStore must be dropped _before_ SwtpmHarness.
+    fn test_store() -> Result<(SwtpmHarness, TpmCredStore)> {
+        let harness = SwtpmHarness::new()?;
+        let store = TpmCredStore::new(harness.context()?, &harness.state_path())?;       
+        Ok((harness, store))
     }
 
     fn sign_verify_test(creds: &TpmCreds) -> Result<()> {
@@ -1148,7 +1239,7 @@ mod test {
 
     #[test]
     fn tpm_sign_verify() -> Result<()> {
-        let (store, _dir) = test_store()?;
+        let (_harness, store) = test_store()?;
         let creds = store.gen_node_creds()?;
         sign_verify_test(&creds)
     }
@@ -1167,7 +1258,7 @@ mod test {
 
     #[test]
     fn tpm_encrypt_decrypt() -> Result<()> {
-        let (store, _dir) = test_store()?;
+        let (_harness, store) = test_store()?;
         let creds = store.gen_node_creds()?;
         encrypt_decrypt_test(&creds)
     }
@@ -1192,20 +1283,22 @@ mod test {
 
     #[test]
     fn persistent_handles() {
-        let mut context = Context::for_test().unwrap();
+        let harness = SwtpmHarness::new().unwrap();
+        let mut context = harness.context().unwrap();
         let _all_handles = context.persistent_handles().unwrap();
     }
 
     #[test]
     fn first_free_persistent() -> Result<()> {
-        let mut context = Context::for_test()?;
+        let harness = SwtpmHarness::new()?;
+        let mut context = harness.context()?;
         let _first = context.unused_persistent_primary_key()?;
         Ok(())
     }
 
     #[test]
     fn persist_key() -> Result<()> {
-        let (store, _dir) = test_store()?;
+        let (_harness, store) = test_store()?;
         let cookie = Cookie::random()?;
         let params = KeyParams::with_unique(cookie.as_slice());
         let pair = store.gen_key(params)?;
@@ -1217,14 +1310,14 @@ mod test {
     /// Tests that the same node key is returned by after a cred store is dropped and recreated.
     #[test]
     fn node_key_persisted() -> Result<()> {
-        let (store, dir) = test_store()?;
+        let (harness, store) = test_store()?;
         let expected = {
             let creds = store.node_creds()?;
             // This contains a hash of the public keys in `creds`, so it strongly identifies them.
             creds.owner()
         };
         drop(store);
-        let store = TpmCredStore::new(Context::for_test()?, state_path(dir.path()))?;
+        let store = TpmCredStore::new(harness.context()?, harness.state_path())?;
         let creds = store.node_creds()?;
         let actual = creds.owner();
         assert_eq!(expected, actual);
@@ -1236,13 +1329,13 @@ mod test {
     #[test]
     fn root_key_persisted() -> Result<()> {
         const PASSWORD: &str = "Scaramouch";
-        let (store, dir) = test_store()?;
+        let (harness, store) = test_store()?;
         let expected = {
             let creds = store.gen_root_creds(PASSWORD)?;
             creds.owner()
         };
         drop(store);
-        let store = TpmCredStore::new(Context::for_test()?, state_path(dir.path()))?;
+        let store = TpmCredStore::new(harness.context()?, harness.state_path())?;
         let creds = store.root_creds(PASSWORD)?;
         let actual = creds.owner();
         assert_eq!(expected, actual);
@@ -1253,10 +1346,10 @@ mod test {
 
     #[test]
     fn root_key_not_returned_when_password_wrong() -> Result<()> {
-        let (store, dir) = test_store()?;
+        let (harness, store) = test_store()?;
         store.gen_root_creds("Galileo")?;
         drop(store);
-        let store = TpmCredStore::new(Context::for_test()?, state_path(dir.path()))?;
+        let store = TpmCredStore::new(harness.context()?, harness.state_path())?;
         let creds = store.root_creds("Figaro")?;
         assert!(sign_verify_test(&creds).is_err());
         assert!(encrypt_decrypt_test(&creds).is_err());