Browse Source

Added methods to CredStore for exporting and
importing root creds.

Matthew Carr 2 years ago
parent
commit
1c4ab95b14
2 changed files with 388 additions and 34 deletions
  1. 8 0
      crates/btnode/src/crypto/mod.rs
  2. 380 34
      crates/btnode/src/crypto/tpm.rs

+ 8 - 0
crates/btnode/src/crypto/mod.rs

@@ -898,6 +898,7 @@ pub(crate) trait Creds: CredsPriv + CredsPub {}
 /// A trait for types which store credentials.
 pub(crate) trait CredStore {
     type CredHandle: Creds;
+    type ExportedCreds: Serialize + for<'de> Deserialize<'de>;
 
     /// Returns the node credentials. If credentials haven't been generated, they are generated
     /// stored and returned.
@@ -908,6 +909,13 @@ pub(crate) trait CredStore {
     /// Generates the root credentials and protects them using the given password. If the root
     /// credentials have already been generated then an error is returned.
     fn gen_root_creds(&self, password: &str) -> Result<Self::CredHandle>;
+    fn storage_key(&self) -> Result<AsymKeyPub<Encrypt>>;
+    fn export_root_creds(
+        &self, root_creds: &Self::CredHandle, password: &str, new_parent: &AsymKeyPub<Encrypt>
+    ) -> Result<Self::ExportedCreds>;
+    fn import_root_creds(
+        &self, password: &str, exported: Self::ExportedCreds
+    ) -> Result<Self::CredHandle>;
 }
 
 pub(crate) fn encrypt_block<C: Encrypter + Decrypter>(

+ 380 - 34
crates/btnode/src/crypto/tpm.rs

@@ -13,7 +13,7 @@ use std::{
     mem::size_of,
 };
 use openssl::{
-    bn::BigNum,
+    bn::{BigNum, BigNumRef},
     hash::Hasher,
     nid::Nid,
 };
@@ -30,7 +30,7 @@ use tss_esapi::{
         session_type::SessionType,
         response_code::Tss2ResponseCode,
         tss::{TPM2_RH_NULL, TPM2_PERSISTENT_FIRST},
-        CapabilityType, Tss2ResponseCodeKind,
+        CapabilityType, Tss2ResponseCodeKind, CommandCode,
     },
     tcti_ldr::{TctiNameConf, TabrmdConfig},
     interface_types::{
@@ -38,7 +38,7 @@ use tss_esapi::{
         algorithm::HashingAlgorithm,
         key_bits::RsaKeyBits,
         dynamic_handles::Persistent,
-        session_handles::AuthSession,
+        session_handles::{AuthSession, PolicySession},
     },
     structures::{
         Auth,
@@ -55,13 +55,20 @@ use tss_esapi::{
         SignatureScheme,
         RsaDecryptionScheme,
         Data,
-        CapabilityData,
+        CapabilityData, EncryptedSecret,
     },
     attributes::{
         object::ObjectAttributes, SessionAttributesBuilder,
     },
-    handles::{KeyHandle, PersistentTpmHandle, TpmHandle},
+    handles::{
+        KeyHandle,
+        PersistentTpmHandle,
+        TpmHandle,
+        ObjectHandle
+    },
+    traits::{Marshall, UnMarshall},
 };
+use serde::ser;
 
 impl From<tss_esapi::Error> for Error {
     fn from(err: tss_esapi::Error) -> Self {
@@ -96,7 +103,11 @@ trait ContextExt {
 
     fn key_handle(&mut self, tpm_handle: TpmHandle) -> Result<KeyHandle>;
 
+    fn set_encrypt_decrypt(&mut self, session: AuthSession) -> Result<()>;
+
     fn start_default_auth_session(&mut self) -> Result<AuthSession>;
+
+    fn start_policy_session(&mut self, is_trial: IsTrial) -> Result<PolicySession>;
 }
 
 impl ContextExt for Context {
@@ -206,6 +217,15 @@ impl ContextExt for Context {
         Ok(())
     }
 
+    fn set_encrypt_decrypt(&mut self, session: AuthSession) -> Result<()> {
+        let (attributes, mask) = SessionAttributesBuilder::new()
+            .with_decrypt(true)
+            .with_encrypt(true)
+            .build();
+        self.tr_sess_set_attributes(session, attributes, mask)?;
+        Ok(())
+    }
+
     fn start_default_auth_session(&mut self) -> Result<AuthSession> {
         let session = self.start_auth_session(
             None,
@@ -216,16 +236,24 @@ impl ContextExt for Context {
             HashingAlgorithm::Sha256,
         )?
         .ok_or_else(|| Error::custom("empty session handle received from TPM"))?;
-
-        let (attributes, mask) = SessionAttributesBuilder::new()
-            .with_decrypt(true)
-            .with_encrypt(true)
-            .build();
-        self.tr_sess_set_attributes(session, attributes, mask)?;
-
+        self.set_encrypt_decrypt(session)?;
         Ok(session)
     }
 
+    fn start_policy_session(&mut self, is_trial: IsTrial) -> Result<PolicySession> {
+        let session = self.start_auth_session(
+            None,
+            None,
+            None,
+            is_trial.into(),
+            SymmetricDefinition::AES_256_CFB,
+            HashingAlgorithm::Sha256,
+        )?
+        .ok_or_else(|| Error::custom("empty session handle received from TPM"))?;
+        self.set_encrypt_decrypt(session)?;
+        Ok(session.try_into()?)
+    }
+
     /// Loads the public information from the persistent object stored in the TPM referred to by
     /// `tpm_handle` and returns the `KeyHandle` for this object. If the TPM handle refers to an
     /// object which is not actually a key, then an error is returned.
@@ -237,6 +265,20 @@ impl ContextExt for Context {
     }
 }
 
+enum IsTrial {
+    True,
+    False,
+}
+
+impl From<IsTrial> for SessionType {
+    fn from(is_trial: IsTrial) -> Self {
+        match is_trial {
+            IsTrial::True => SessionType::Trial,
+            IsTrial::False => SessionType::Policy,
+        }
+    }
+}
+
 trait DigestExt {
     fn empty() -> Digest;
 }
@@ -281,6 +323,82 @@ impl Cookie {
     }
 }
 
+pub struct ExportedKeyPair<S: Scheme> {
+    scheme: S,
+    public: Public,
+    private: tss_esapi::structures::Private,
+    secret: EncryptedSecret, 
+}
+
+impl<S: Scheme> ExportedKeyPair<S> {
+    const FIELDS: &'static [&'static str] = &[
+        "scheme",
+        "public",
+        "private",
+        "secret",
+    ];
+}
+
+impl<S: Scheme> Serialize for ExportedKeyPair<S> {
+    fn serialize<T: Serializer>(&self, ser: T) -> std::result::Result<T::Ok, T::Error> {
+        let vec = self.public.marshall().map_err(ser::Error::custom)?;
+        let mut struct_ser = ser.serialize_struct(stringify!(ExportedKey), Self::FIELDS.len())?;
+        struct_ser.serialize_field(Self::FIELDS[0], &self.scheme)?;
+        struct_ser.serialize_field(Self::FIELDS[1], &vec)?;
+        struct_ser.serialize_field(Self::FIELDS[2], self.private.value())?;
+        struct_ser.serialize_field(Self::FIELDS[3], self.secret.value())?;
+        struct_ser.end()
+    }
+}
+
+impl<'de, S: Scheme> Deserialize<'de> for ExportedKeyPair<S> {
+    fn deserialize<D: Deserializer<'de>>(de: D) -> std::result::Result<Self, D::Error> {
+        struct StructVisitor<S: Scheme>(PhantomData<S>);
+
+        impl<'de, S: Scheme> Visitor<'de> for StructVisitor<S> {
+            type Value = ExportedKeyPair<S>;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_fmt(format_args!("struct {}", stringify!(ExportedKey)))
+            }
+
+            fn visit_seq<V: SeqAccess<'de>>(
+                self, mut seq: V
+            ) -> std::result::Result<Self::Value, V::Error> {
+                fn conv_err<T, E: de::Error>(
+                    result: std::result::Result<T, tss_esapi::Error>
+                ) -> std::result::Result<T, E> {
+                    result.map_err(de::Error::custom)
+                }
+
+                let scheme: S = seq.next_element()?
+                    .ok_or_else(|| de::Error::missing_field(ExportedKeyPair::<S>::FIELDS[0]))?;
+                let public: Box<[u8]> = seq.next_element()?
+                    .ok_or_else(|| de::Error::missing_field(ExportedKeyPair::<S>::FIELDS[1]))?;
+                let public = conv_err(Public::unmarshall(public.as_ref()))?;
+                let private: Box<[u8]> = seq.next_element()?
+                    .ok_or_else(|| de::Error::missing_field(ExportedKeyPair::<S>::FIELDS[2]))?;
+                let secret: Box<[u8]> = seq.next_element()?
+                    .ok_or_else(|| de::Error::missing_field(ExportedKeyPair::<S>::FIELDS[3]))?;
+                Ok(ExportedKeyPair {
+                    scheme,
+                    public,
+                    private: conv_err(private.as_ref().try_into())?,
+                    secret: conv_err(secret.as_ref().try_into())?,
+                })
+            }
+        }
+
+        de.deserialize_struct(stringify!(ExportedKey), Self::FIELDS, StructVisitor(PhantomData))
+    }
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct ExportedCreds {
+    sign: ExportedKeyPair<Sign>,
+    enc: ExportedKeyPair<Encrypt>,
+}
+
 /// A public/private key pair in a form which can be serialized and deserialized.
 #[derive(Serialize, Clone)]
 struct StoredKeyPair<S: Scheme> {
@@ -288,6 +406,8 @@ struct StoredKeyPair<S: Scheme> {
     private: TPM2_HANDLE,
 }
 
+// I was unable to use `derive(Deserialize)` on `StoredKeyPair`, so I resorted to implementing it
+// manually.
 impl<'de, S: Scheme> Deserialize<'de> for StoredKeyPair<S> {
     fn deserialize<D: Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
         const FIELDS: &[&str] = &["public", "private"];
@@ -442,6 +562,7 @@ struct KeyBuilder<'a, S: Scheme> {
     parent: Parent,
     restricted: bool,
     symmetric: SymmetricDefinitionObject,
+    rsa_exponent: u32,
 }
 
 impl<'a, S: Scheme> KeyBuilder<'a, S> {
@@ -456,9 +577,17 @@ impl<'a, S: Scheme> KeyBuilder<'a, S> {
             parent: Parent::Seed(Hierarchy::Owner),
             restricted: false,
             symmetric: SymmetricDefinitionObject::Null,
+            rsa_exponent: Rsa::EXP,
         }
     }
 
+    fn for_storage_key(scheme: S, unique: &'a [u8]) -> KeyBuilder<'a, S> {
+        KeyBuilder::new(scheme, unique)
+            .with_allow_dup(false)
+            .with_restricted(true)
+            .with_symmetric(SymmetricDefinitionObject::AES_256_CFB)
+    }
+
     fn with_allow_dup(mut self, allow_dup: bool) -> Self {
         self.allow_dup = allow_dup;
         self
@@ -494,6 +623,11 @@ impl<'a, S: Scheme> KeyBuilder<'a, S> {
         self
     }
 
+    fn with_rsa_exponent(mut self, rsa_exponent: u32) -> Self {
+        self.rsa_exponent = rsa_exponent;
+        self
+    }
+
     fn rsa_template(&self) -> Result<Public> {
         let scheme_enum = self.scheme.as_enum();
         let decrypt = match scheme_enum {
@@ -514,7 +648,7 @@ impl<'a, S: Scheme> KeyBuilder<'a, S> {
             self.symmetric,
             if self.restricted { RsaScheme::Null } else { scheme_enum.try_into()? },
             self.scheme.key_len().try_into()?,
-            Rsa::EXP.try_into()?, 
+            self.rsa_exponent.try_into()?, 
         );
         let unique = PublicKeyRsa::try_from(self.unique)?;
         let public = Public::Rsa {
@@ -637,8 +771,8 @@ pub(crate) struct TpmCredStore {
 }
 
 impl TpmCredStore {
-    const SIGN_SCHEME: Sign = Sign::RSA_PSS_3072_SHA_256;
-    const ENCRYPT_SCHEME: Encrypt = Encrypt::RSA_OAEP_3072_SHA_256;
+    const SIGN_SCHEME: Sign = Sign::RSA_PSS_2048_SHA_256;
+    const ENCRYPT_SCHEME: Encrypt = Encrypt::RSA_OAEP_2048_SHA_256;
 
     pub(crate) fn new<P: AsRef<Path>>(mut context: Context, state_path: P) -> Result<TpmCredStore> {
         let storage = Storage::load_or_init(state_path.as_ref())?;
@@ -698,21 +832,30 @@ impl TpmCredStore {
         {
             let guard = self.state.read()?;
             if let Some(storage_key) = &guard.storage_key {
+                // We take this path if the storage key was generated and loaded.
                 return Ok(storage_key.clone());
             }
         }
-        let params = KeyBuilder::new(Self::ENCRYPT_SCHEME, self.cookie.as_slice())
+        {
+            let mut guard = self.state.write()?;
+            if let Some(storage_key) = guard.storage.storage_key.take() {
+                let result = KeyPair::from_stored(&mut guard.context, &storage_key); 
+                guard.storage.storage_key = Some(storage_key);
+                // We take this path if the storage key was generated but not loaded.
+                return result;
+            }
+        }
+        let params = KeyBuilder::for_storage_key(Self::ENCRYPT_SCHEME, self.cookie.as_slice())
             .with_parent(Parent::Seed(Hierarchy::Owner))
-            .with_allow_dup(false)
-            .with_auth(self.cookie.auth())
-            .with_restricted(true)
-            .with_symmetric(SymmetricDefinitionObject::AES_256_CFB);
+            .with_auth(self.cookie.auth());
         let mut guard = self.state.write()?;
         let storage_key = params.build(&mut guard.context)?;
         guard.storage_key = Some(storage_key.clone());
         let tpm_handle = guard.context.persist_key(storage_key.private)?;
         guard.storage.storage_key = Some(storage_key.to_stored(tpm_handle));
         if let Err(err) = self.save_storage(&mut guard) {
+            guard.storage_key = None;
+            guard.storage.storage_key = None;
             guard.context.evict_key(tpm_handle, Some(storage_key.private))?;
             return Err(err);
         }
@@ -743,34 +886,52 @@ impl TpmCredStore {
     fn gen_node_creds(&self) -> Result<TpmCreds> {
         let sign = self.gen_node_sign_key()?;
         let enc = self.gen_node_enc_key()?;
-        let creds = TpmCreds { sign, enc, state: self.state.clone() };
+        let cred_data = CredData { sign, enc };
+        let creds = TpmCreds::new(cred_data, &self.state);
         self.persist(&creds, |storage, handles| storage.node = Some(handles))?;
         Ok(creds)
     }
 
-    fn gen_root_sign_key(&self, password: &str) -> Result<KeyPair<Sign>> {
-        let unique = rand_vec(Self::ENCRYPT_SCHEME.key_len() as usize)?;
+    /// Returns the digest for the policy which allows a key to be duplicated provided that the
+    /// caller supplies the correct password (authValue) for the key being duplicated.
+    fn dup_with_password_policy(&self) -> Result<Digest> {
+        let mut guard = self.state.write()?;
+        let policy_session = guard.context.start_policy_session(IsTrial::True)?;
+        guard.context.execute_with_session(None, |ctx| {
+            ctx.policy_password(policy_session)?;
+            ctx.policy_command_code(policy_session, CommandCode::Duplicate)?;
+            Ok(ctx.policy_get_digest(policy_session)?)
+        })
+    }
+
+    fn gen_root_sign_key(&self, password: &str, policy: Digest) -> Result<KeyPair<Sign>> {
+        let scheme = Self::SIGN_SCHEME;
+        let unique = rand_vec(scheme.key_len() as usize)?;
         let storage_key = self.get_or_init_storage_key()?;
-        let params = KeyBuilder::new(Self::SIGN_SCHEME, unique.as_slice())
+        let params = KeyBuilder::new(scheme, unique.as_slice())
             .with_parent(Parent::Key(storage_key.private))
             .with_allow_dup(true)
-            .with_auth(password.as_bytes().try_into()?);
+            .with_auth(password.as_bytes().try_into()?)
+            .with_policy_digest(policy);
         self.gen_key(params)
     }
 
-    fn gen_root_enc_key(&self, password: &str) -> Result<KeyPair<Encrypt>> {
-        let unique = rand_vec(Self::ENCRYPT_SCHEME.key_len() as usize)?;
+    fn gen_root_enc_key(&self, password: &str, policy: Digest) -> Result<KeyPair<Encrypt>> {
+        let scheme = Self::ENCRYPT_SCHEME;
+        let unique = rand_vec(scheme.key_len() as usize)?;
         let storage_key = self.get_or_init_storage_key()?;
-        let params = KeyBuilder::new(Self::ENCRYPT_SCHEME, unique.as_slice())
+        let params = KeyBuilder::new(scheme, unique.as_slice())
             .with_parent(Parent::Key(storage_key.private))
             .with_allow_dup(true)
-            .with_auth(password.as_bytes().try_into()?);
+            .with_auth(password.as_bytes().try_into()?)
+            .with_policy_digest(policy);
         self.gen_key(params)
     }
 }
 
 impl CredStore for TpmCredStore {
     type CredHandle = TpmCreds;
+    type ExportedCreds = ExportedCreds;
 
     fn node_creds(&self) -> Result<TpmCreds> {
         {
@@ -804,9 +965,125 @@ impl CredStore for TpmCredStore {
                 return Err(Error::custom("root creds have already been generated"))
             }
         }
-        let sign = self.gen_root_sign_key(password)?;
-        let enc = self.gen_root_enc_key(password)?;
-        let creds = TpmCreds { sign, enc, state: self.state.clone() };
+        let policy = self.dup_with_password_policy()?;
+        let sign = self.gen_root_sign_key(password, policy.clone())?;
+        let enc = self.gen_root_enc_key(password, policy)?;
+        let cred_data = CredData { sign, enc };
+        let creds = TpmCreds::new(cred_data, &self.state);
+        self.persist(&creds, |storage, handles| storage.root = Some(handles))?;
+        Ok(creds)
+    }
+
+    fn storage_key(&self) -> Result<AsymKeyPub<Encrypt>> {
+        let pair = self.get_or_init_storage_key()?;
+        Ok(pair.public)
+    }
+
+    fn export_root_creds(
+        &self, root_creds: &TpmCreds, password: &str, new_parent: &AsymKeyPub<Encrypt>
+    ) -> Result<ExportedCreds> {
+        fn export_key<S: Scheme>(
+            context: &mut Context,
+            key_pair: &KeyPair<S>,
+            new_parent: KeyHandle,
+            key_auth: Auth,
+            policy_session: PolicySession,
+        ) -> Result<ExportedKeyPair<S>> {
+            let result: Result<()> = context.execute_with_session(None, |ctx| {
+                ctx.policy_password(policy_session)?;
+                ctx.policy_command_code(policy_session, CommandCode::Duplicate)?;
+                Ok(())
+            });
+            result?;
+            let (public, ..) = context.read_public(key_pair.private)?;
+            context.execute_with_session(Some(policy_session.into()), |ctx| {
+                let obj_handle = ObjectHandle::from(key_pair.private);
+                ctx.tr_set_auth(obj_handle, key_auth)?;
+                let (_, private, secret) = ctx.duplicate(
+                    obj_handle,
+                    new_parent.into(),
+                    None,
+                    SymmetricDefinitionObject::Null,
+                )?;
+                Ok(ExportedKeyPair {
+                    scheme: key_pair.public.scheme,
+                    public,
+                    private,
+                    secret,
+                })
+            })
+        }
+
+        let new_parent = new_parent.storage_key_public()?;
+        let mut guard = self.state.write()?;
+        if let Some(storage_key) = guard.storage_key.take() {
+            // Save memory by flushing the storage key from the TPM's RAM.
+            guard.context.flush_context(storage_key.private.into())?;
+        }
+        let new_parent_handle = guard.context.load_external_public(new_parent, Hierarchy::Null)?;
+        let policy_session = guard.context.start_policy_session(IsTrial::False)?;
+        let auth = Auth::try_from(password.as_bytes())?;
+        let sign = export_key(
+            &mut guard.context,
+            &root_creds.sign,
+            new_parent_handle,
+            auth.clone(),
+            policy_session,
+        )?;
+        let enc = export_key(
+            &mut guard.context,
+            &root_creds.enc,
+            new_parent_handle,
+            auth,
+            policy_session,
+        )?;
+        Ok(ExportedCreds { sign, enc })
+    }
+
+    fn import_root_creds(&self, password: &str, exported: ExportedCreds) -> Result<TpmCreds> {
+        fn import_key<S: Scheme>(
+            context: &mut Context,
+            exported: ExportedKeyPair<S>,
+            new_parent: KeyHandle,
+            auth: Auth,
+        ) -> Result<KeyPair<S>> {
+            let private = context.import(
+                new_parent.into(),
+                None,
+                exported.public.clone(),
+                exported.private,
+                exported.secret,
+                SymmetricDefinitionObject::Null,
+            )?;
+            let key_handle = context.load(
+                new_parent,
+                private,
+                exported.public.clone(),
+            )?;
+            context.tr_set_auth(key_handle.into(), auth)?;
+            let public = AsymKeyPub::try_from(exported.public, exported.scheme)?;
+            Ok(KeyPair { public, private: key_handle })
+        }
+
+        let storage_key = self.get_or_init_storage_key()?;
+        let creds = {
+            let mut guard = self.state.write()?;
+            let auth = Auth::try_from(password.as_bytes())?;
+            let sign = import_key(
+                &mut guard.context,
+                exported.sign,
+                storage_key.private,
+                auth.clone()
+            )?;
+            let enc = import_key(
+                &mut guard.context,
+                exported.enc,
+                storage_key.private,
+                auth,
+            )?;
+            let cred_data = CredData { sign, enc, };
+            TpmCreds::new(cred_data, &self.state)
+        };
         self.persist(&creds, |storage, handles| storage.root = Some(handles))?;
         Ok(creds)
     }
@@ -826,6 +1103,26 @@ impl<S: Scheme> AsymKeyPub<S> {
             _ => Err(Error::custom("Unsupported key type returned by TPM")),
         }
     }
+
+    fn storage_key_public(&self) -> Result<Public> {
+        fn from_rsa<S: Scheme>(
+            scheme: S, rsa: openssl::rsa::Rsa<crypto::Public>
+        ) -> Result<Public> {
+            let exponent = rsa.e().try_into_u32()?;
+            let modulus = rsa.n().to_vec();
+            let builder = KeyBuilder::for_storage_key(scheme, &modulus)
+                .with_rsa_exponent(exponent);
+            builder.rsa_template()
+        }
+        match self.scheme.as_enum() {
+            SchemeKind::Encrypt(inner) => match inner {
+                Encrypt::RsaEsOaep(scheme) => from_rsa(scheme, self.pkey.rsa()?),
+            }
+            SchemeKind::Sign(inner) => match inner {
+                Sign::RsaSsaPss(scheme) => from_rsa(scheme, self.pkey.rsa()?),
+            }
+        } 
+    }
 }
 
 trait NidExt {
@@ -844,6 +1141,26 @@ trait NidExt {
 
 impl NidExt for Nid {}
 
+trait BigNumRefExt {
+    fn try_into_u32(self) -> Result<u32>;
+}
+
+impl BigNumRefExt for &BigNumRef {
+    fn try_into_u32(self) -> Result<u32> {
+        let data = self.to_vec();
+        if data.len() > 4 {
+            return Err(Error::custom(format!("data was too long: {}", data.len())));
+        }
+        let mut buf = [0u8; 4];
+        // Note that BigNum data is stored in big endian format, so padding zeros go at the
+        // beginning of the buffer.
+        let subslice = &mut buf[4-data.len()..];
+        subslice.copy_from_slice(data.as_slice());
+        let int = u32::from_be_bytes(buf);
+        Ok(int)
+    }
+}
+
 trait MessageDigestExt {
     fn hash_algo(&self) -> Result<HashingAlgorithm>;
     fn hash_scheme(&self) -> Result<HashScheme> {
@@ -909,7 +1226,11 @@ pub(crate) struct TpmCreds {
 
 impl TpmCreds {
     fn new(key_handles: CredData, state: &Arc<RwLock<State>>) -> TpmCreds {
-        TpmCreds { sign: key_handles.sign, enc: key_handles.enc, state: state.clone() }
+        TpmCreds {
+            sign: key_handles.sign,
+            enc: key_handles.enc,
+            state: state.clone(),
+        }
     }
 }
 
@@ -1163,7 +1484,7 @@ active_pcr_banks = sha256
     /// Displays the message associated with a TSS2 return code.
     //#[test]
     fn print_error_message() {
-        const RC: TSS2_RC = 0x00000101;
+        const RC: TSS2_RC = 0x00000982;
         let msg = tss2_rc_decode(RC);
         println!("{}", msg);
     }
@@ -1415,4 +1736,29 @@ active_pcr_banks = sha256
         assert!(encrypt_decrypt_test(&creds).is_err());
         Ok(())
     }
+
+    #[test]
+    fn root_key_export_import() -> Result<()> {
+        const PASSWORD: &str = "Frobinate";
+        let (_src_harness, src_store) = test_store()?;
+        let src_root_creds = src_store.gen_root_creds(PASSWORD)?;
+
+        let (_dest_harness, dest_store) = test_store()?;
+        let dest_storage_key = dest_store.storage_key()?;
+
+        let exported = src_store.export_root_creds(&src_root_creds, PASSWORD, &dest_storage_key)?;
+        let vec = to_vec(&exported)?;
+        let mut slice = vec.as_slice();
+        let exported = read_from(&mut slice)?;
+        let dest_root_creds = dest_store.import_root_creds(PASSWORD, exported)?;
+
+        let message = rand_vec(TpmCredStore::ENCRYPT_SCHEME.key_len() as usize / 2)?;
+        let sig = dest_root_creds.sign([message.as_slice()].into_iter())?;
+        src_root_creds.verify([message.as_slice()].into_iter(), sig.as_slice())?;
+
+        let ct = src_root_creds.encrypt(message.as_slice())?;
+        let pt = dest_root_creds.decrypt(ct.as_slice())?;
+        assert_eq!(message.as_slice(), pt.as_slice());
+        Ok(())
+    }
 }