소스 검색

Added a `CredStore` which stores credentials in a file.

Matthew Carr 1 년 전
부모
커밋
ff97eb33ce
5개의 변경된 파일707개의 추가작업 그리고 84개의 파일을 삭제
  1. 2 2
      crates/btfsd/src/main.rs
  2. 153 40
      crates/btlib/src/crypto.rs
  3. 53 0
      crates/btlib/src/crypto/envelope.rs
  4. 499 0
      crates/btlib/src/crypto/file_cred_store.rs
  5. 0 42
      crates/btlib/src/crypto/tpm.rs

+ 2 - 2
crates/btfsd/src/main.rs

@@ -112,7 +112,7 @@ mod tests {
     use btlib_tests::TpmCredStoreHarness;
     use btmsg::{BlockAddr, Transmitter};
     use btserde::from_slice;
-    use std::{future::ready, net::Ipv4Addr, time::Duration};
+    use std::{future::ready, net::Ipv6Addr, time::Duration};
     use swtpm_harness::SwtpmHarness;
     use tempdir::TempDir;
 
@@ -132,7 +132,7 @@ mod tests {
     }
 
     const ROOT_PASSWD: &str = "existential_threat";
-    const LOCALHOST: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST);
+    const LOCALHOST: IpAddr = IpAddr::V6(Ipv6Addr::LOCALHOST);
     const BT_DIR: &str = "bt";
 
     async fn test_case(

+ 153 - 40
crates/btlib/src/crypto.rs

@@ -6,6 +6,9 @@ pub mod x509;
 pub use merkle_stream::MerkleStream;
 pub mod secret_stream;
 pub use secret_stream::SecretStream;
+mod envelope;
+pub mod file_cred_store;
+pub use envelope::Envelope;
 //mod sign_stream;
 //pub use sign_stream::SignStream;
 
@@ -22,6 +25,7 @@ use openssl::{
     error::ErrorStack,
     hash::{hash, DigestBytes, Hasher, MessageDigest},
     nid::Nid,
+    pkcs5::pbkdf2_hmac,
     pkey::{HasPrivate, HasPublic, PKey, PKeyRef},
     rand::rand_bytes,
     rsa::{Padding as OpensslPadding, Rsa as OsslRsa},
@@ -30,7 +34,7 @@ use openssl::{
 };
 use serde::{
     de::{self, DeserializeOwned, Deserializer, SeqAccess, Visitor},
-    ser::{SerializeStruct, Serializer},
+    ser::{Error as SerError, SerializeStruct, Serializer},
 };
 use std::{
     cell::RefCell,
@@ -41,7 +45,7 @@ use std::{
     sync::Arc,
 };
 use strum_macros::{Display, EnumDiscriminants, FromRepr};
-use zeroize::ZeroizeOnDrop;
+use zeroize::{ZeroizeOnDrop, Zeroizing};
 
 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
 pub struct Ciphertext<T> {
@@ -843,13 +847,13 @@ impl AsMut<[u8]> for Signature {
 }
 
 #[derive(Serialize, Deserialize)]
-struct TaggedCiphertext<T, U> {
+pub struct TaggedCiphertext<T, U> {
     aad: U,
     ciphertext: Ciphertext<T>,
     tag: Vec<u8>,
 }
 
-#[derive(EnumDiscriminants, ZeroizeOnDrop)]
+#[derive(EnumDiscriminants, ZeroizeOnDrop, Serialize, Deserialize, PartialEq, Eq)]
 #[strum_discriminants(name(AeadKeyKind))]
 #[strum_discriminants(derive(Serialize, Deserialize))]
 pub enum AeadKey {
@@ -873,6 +877,56 @@ impl AeadKeyKind {
     }
 }
 
+#[derive(Serialize, Deserialize)]
+/// Parameters used to derive cryptographic keys from passwords.
+pub struct DerivationParams {
+    iter: usize,
+    hash: HashKind,
+    kind: AeadKeyKind,
+    salt: Vec<u8>,
+    iv: Vec<u8>,
+}
+
+impl DerivationParams {
+    const PBKDF2_ITER: usize = 1000000;
+    const PBKDF2_HASH: HashKind = HashKind::Sha2_256;
+    /// The [AeadKeyKind] of the key derived from this struct.
+    pub const EXPORT_KEY_KIND: AeadKeyKind = AeadKeyKind::AesGcm256;
+
+    /// Creates a new struct containing the default value of all parameters.
+    pub fn new() -> Result<DerivationParams> {
+        const_assert!(
+            DerivationParams::PBKDF2_HASH.len() == DerivationParams::EXPORT_KEY_KIND.key_len()
+        );
+        Ok(DerivationParams {
+            iter: Self::PBKDF2_ITER,
+            hash: Self::PBKDF2_HASH,
+            kind: Self::EXPORT_KEY_KIND,
+            salt: rand_vec(Self::PBKDF2_HASH.len())?,
+            iv: rand_vec(Self::EXPORT_KEY_KIND.iv_len())?,
+        })
+    }
+
+    /// Returns an HMAC of the given password based on the parameters in this struct.
+    pub fn hmac(&self, password: &str) -> Result<Zeroizing<[u8; Self::EXPORT_KEY_KIND.key_len()]>> {
+        let mut key = Zeroizing::new([0u8; Self::EXPORT_KEY_KIND.key_len()]);
+        pbkdf2_hmac(
+            password.as_bytes(),
+            self.salt.as_slice(),
+            self.iter,
+            self.hash.into(),
+            key.as_mut_slice(),
+        )?;
+        Ok(key)
+    }
+
+    /// Derives an [AeadKey] from the given password using the parameters in this struct.
+    fn derive_key(&self, password: &str) -> Result<AeadKey> {
+        let key = self.hmac(password)?;
+        AeadKey::copy_components(self.kind, key.as_slice(), &self.iv)
+    }
+}
+
 fn array_from<const N: usize>(slice: &[u8]) -> Result<[u8; N]> {
     let slice_len = slice.len();
     btensure!(
@@ -1084,7 +1138,7 @@ impl TryFrom<u32> for BitLen {
 /// A Cryptographic Scheme. This is a common type for operations such as encrypting, decrypting,
 /// signing and verifying.
 pub trait Scheme:
-    for<'de> Deserialize<'de> + Serialize + Copy + std::fmt::Debug + PartialEq + Into<Self::Kind>
+    DeserializeOwned + Serialize + Copy + std::fmt::Debug + PartialEq + Into<Self::Kind>
 {
     type Kind: Scheme;
     fn as_enum(self) -> SchemeKind;
@@ -1423,46 +1477,61 @@ impl<S: Scheme> AsymKey<Private, S> {
     }
 }
 
-impl<'de, S: Scheme> Deserialize<'de> for AsymKey<Public, S> {
-    fn deserialize<D: Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
-        const FIELDS: &[&str] = &["scheme", "pkey"];
+macro_rules! impl_asym_key_serialize {
+    ($privacy:ty, $ser_lambda:expr) => {
+        impl<S: Scheme> Serialize for AsymKey<$privacy, S> {
+            fn serialize<T: Serializer>(&self, s: T) -> std::result::Result<T::Ok, T::Error> {
+                let mut struct_s = s.serialize_struct(stringify!(AsymKey), 2)?;
+                struct_s.serialize_field("scheme", &self.scheme)?;
+                let der = $ser_lambda(&self.pkey).map_err(T::Error::custom)?;
+                struct_s.serialize_field("pkey", der.as_slice())?;
+                struct_s.end()
+            }
+        }
+    };
+}
 
-        struct StructVisitor<S: Scheme>(PhantomData<S>);
+impl_asym_key_serialize!(Public, |pkey: &PKey<Public>| pkey.public_key_to_der());
+impl_asym_key_serialize!(Private, |pkey: &PKey<Private>| pkey.private_key_to_der());
 
-        impl<'de, S: Scheme> Visitor<'de> for StructVisitor<S> {
-            type Value = AsymKey<Public, S>;
+macro_rules! impl_asym_key_deserialize {
+    ($privacy:ty) => {
+        impl<'de, S: Scheme> Deserialize<'de> for AsymKey<$privacy, S> {
+            fn deserialize<D: Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
+                const FIELDS: &[&str] = &["scheme", "pkey"];
 
-            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
-                formatter.write_fmt(format_args!("struct {}", stringify!(AsymKey)))
-            }
+                struct StructVisitor<S: Scheme>(PhantomData<S>);
 
-            fn visit_seq<V: SeqAccess<'de>>(
-                self,
-                mut seq: V,
-            ) -> std::result::Result<Self::Value, V::Error> {
-                let scheme: S = seq
-                    .next_element()?
-                    .ok_or_else(|| de::Error::missing_field(FIELDS[0]))?;
-                let der: Vec<u8> = seq
-                    .next_element()?
-                    .ok_or_else(|| de::Error::missing_field(FIELDS[1]))?;
-                AsymKey::<Public, _>::new(scheme, der.as_slice()).map_err(de::Error::custom)
+                impl<'de, S: Scheme> Visitor<'de> for StructVisitor<S> {
+                    type Value = AsymKey<$privacy, S>;
+
+                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                        formatter.write_fmt(format_args!("struct {}", stringify!(AsymKey)))
+                    }
+
+                    fn visit_seq<V: SeqAccess<'de>>(
+                        self,
+                        mut seq: V,
+                    ) -> std::result::Result<Self::Value, V::Error> {
+                        let scheme: S = seq
+                            .next_element()?
+                            .ok_or_else(|| de::Error::missing_field(FIELDS[0]))?;
+                        let der: Vec<u8> = seq
+                            .next_element()?
+                            .ok_or_else(|| de::Error::missing_field(FIELDS[1]))?;
+                        AsymKey::<$privacy, _>::new(scheme, der.as_slice())
+                            .map_err(de::Error::custom)
+                    }
+                }
+
+                d.deserialize_struct(stringify!(AsymKey), FIELDS, StructVisitor(PhantomData))
             }
         }
-
-        d.deserialize_struct(stringify!(AsymKey), FIELDS, StructVisitor(PhantomData))
-    }
+    };
 }
 
-impl<S: Scheme> Serialize for AsymKey<Public, S> {
-    fn serialize<T: Serializer>(&self, s: T) -> std::result::Result<T::Ok, T::Error> {
-        let mut struct_s = s.serialize_struct(stringify!(AsymKey), 2)?;
-        struct_s.serialize_field("scheme", &self.scheme)?;
-        let der = self.pkey.public_key_to_der().unwrap();
-        struct_s.serialize_field("pkey", der.as_slice())?;
-        struct_s.end()
-    }
-}
+impl_asym_key_deserialize!(Public);
+impl_asym_key_deserialize!(Private);
 
 impl<S: Scheme> PartialEq for AsymKey<Public, S> {
     fn eq(&self, other: &Self) -> bool {
@@ -1568,12 +1637,43 @@ impl Verifier for AsymKey<Public, Sign> {
     }
 }
 
-#[derive(Clone)]
+#[derive(Clone, Serialize)]
 pub struct AsymKeyPair<S: Scheme> {
     public: AsymKey<Public, S>,
     private: AsymKey<Private, S>,
 }
 
+impl<'de, S: Scheme> Deserialize<'de> for AsymKeyPair<S> {
+    fn deserialize<D: Deserializer<'de>>(d: D) -> std::result::Result<Self, D::Error> {
+        const FIELDS: &[&str] = &["public", "private"];
+
+        struct StructVisitor<S: Scheme>(PhantomData<S>);
+
+        impl<'de, S: Scheme> Visitor<'de> for StructVisitor<S> {
+            type Value = AsymKeyPair<S>;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_fmt(format_args!("struct {}", stringify!(AsymKeyPair)))
+            }
+
+            fn visit_seq<V: SeqAccess<'de>>(
+                self,
+                mut seq: V,
+            ) -> std::result::Result<Self::Value, V::Error> {
+                let public: AsymKey<Public, S> = seq
+                    .next_element()?
+                    .ok_or_else(|| de::Error::missing_field(FIELDS[0]))?;
+                let private: AsymKey<Private, S> = seq
+                    .next_element()?
+                    .ok_or_else(|| de::Error::missing_field(FIELDS[1]))?;
+                Ok(AsymKeyPair { public, private })
+            }
+        }
+
+        d.deserialize_struct(stringify!(AsymKey), FIELDS, StructVisitor(PhantomData))
+    }
+}
+
 impl<S: Scheme> AsymKeyPair<S> {
     pub fn new(scheme: S, public_der: &[u8], private_der: &[u8]) -> Result<AsymKeyPair<S>> {
         let public = AsymKey::<Public, _>::new(scheme, public_der)?;
@@ -1688,7 +1788,7 @@ impl PartialEq for ConcretePub {
     }
 }
 
-#[derive(Clone)]
+#[derive(Clone, Serialize, Deserialize)]
 pub struct ConcreteCreds {
     sign: AsymKeyPair<Sign>,
     encrypt: AsymKeyPair<Encrypt>,
@@ -2225,8 +2325,10 @@ impl<C: CredsPriv + CredsPub + Clone> Creds for C {}
 
 /// A trait for types which store credentials.
 pub trait CredStore {
+    /// The type of the credential handle returned by this store.
     type CredHandle: Creds;
-    type ExportedCreds: Serialize + for<'de> Deserialize<'de>;
+    /// The type of the exported credentials returned by this store.
+    type ExportedCreds: Serialize + DeserializeOwned;
 
     /// Returns the node credentials. If credentials haven't been generated, they are generated
     /// stored and returned.
@@ -2237,18 +2339,29 @@ pub 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>;
+    /// Returns a public key which can be used to encrypt data intended only to be accessed by this
+    /// node. The returned key can be given as the `new_parent` parameter to the [export_root_creds]
+    /// method.
     fn storage_key(&self) -> Result<AsymKeyPub<Encrypt>>;
+    /// Exports the root credentials. These can be serialized and persisted external to the
+    /// application and later loaded and deserialized and passed to the [import_root_creds] method.
+    /// The `password` argument must match the value provided when the [root_creds] method was
+    /// called. The `new_parent` argument is the public key of the node that is to import the root
+    /// key, which can be obtained using the [gen_root_creds] method on the importing node.
     fn export_root_creds(
         &self,
         root_creds: &Self::CredHandle,
         password: &str,
         new_parent: &AsymKeyPub<Encrypt>,
     ) -> Result<Self::ExportedCreds>;
+    /// Imports root credentials that were previously created with [export_root_creds]. The provided
+    /// password must match the value that given to that method.
     fn import_root_creds(
         &self,
         password: &str,
         exported: Self::ExportedCreds,
     ) -> Result<Self::CredHandle>;
+    /// Assigns the given [Writecap] to the node credentials referred to by the given handle.
     fn assign_node_writecap(&self, handle: &mut Self::CredHandle, writecap: Writecap)
         -> Result<()>;
 }

+ 53 - 0
crates/btlib/src/crypto/envelope.rs

@@ -0,0 +1,53 @@
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
+
+use crate::{
+    crypto::{Ciphertext, Decrypter, DecrypterExt, Encrypter, EncrypterExt, SymKey, SymKeyKind},
+    Result,
+};
+
+#[derive(Serialize, Deserialize)]
+/// A struct which contains the cipher text for a message along with the ciphertext of the key that
+/// was used to encrypt the message. This is useful for encrypting messages that are too long to be
+/// encrypted using an asymmetric encryption scheme.
+pub struct Envelope<T> {
+    /// The ciphertext of the key used to encrypt the payload.
+    key: Ciphertext<SymKey>,
+    /// The ciphertext of the message encrypted using a symmetric cipher.
+    payload: Ciphertext<T>,
+}
+
+impl<T: Serialize + DeserializeOwned> Envelope<T> {
+    /// Creates a new [Envelope] containing `plaintext` whose symmetric key is encrypted using
+    /// `encrypter`.
+    pub fn new<E: Encrypter>(plaintext: &T, encrypter: E) -> Result<Envelope<T>> {
+        let symkey = SymKey::generate(SymKeyKind::default())?;
+        let payload = symkey.ser_encrypt(plaintext)?;
+        let key = encrypter.ser_encrypt(&symkey)?;
+        Ok(Envelope { key, payload })
+    }
+
+    /// Uses `decrypter` to decrypt the key in this [Envelope] and uses this key to decrypt and
+    /// return the original message.
+    pub fn open<D: Decrypter>(&self, decrypter: D) -> Result<T> {
+        let symkey = decrypter.ser_decrypt(&self.key)?;
+        symkey.ser_decrypt(&self.payload)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::crypto::{envelope::Envelope, ConcreteCreds};
+
+    #[test]
+    /// Tests that a message can be extracted from an [Envelope] when the proper credentials are
+    /// used.
+    fn encrypt_decrypt() {
+        let creds = ConcreteCreds::generate().unwrap();
+        let expected = "We attack at the crack of noon.".to_string();
+
+        let envelope = Envelope::new(&expected, &creds).unwrap();
+        let actual = envelope.open(&creds).unwrap();
+
+        assert_eq!(expected, actual);
+    }
+}

+ 499 - 0
crates/btlib/src/crypto/file_cred_store.rs

@@ -0,0 +1,499 @@
+use crate::{
+    crypto::{
+        AsymKeyPair, AsymKeyPub, ConcreteCreds, CredStore, DerivationParams, Encrypt, Envelope,
+        Scheme, TaggedCiphertext,
+    },
+    error::DisplayErr,
+    Result,
+};
+use btserde::{read_from, write_to};
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+use std::{
+    fmt::Display,
+    fs::{File, Metadata, OpenOptions},
+    io::{self, BufReader, BufWriter},
+    os::unix::fs::PermissionsExt,
+    path::{Path, PathBuf},
+    sync::{Arc, RwLock},
+};
+use zeroize::{Zeroize, Zeroizing};
+
+pub use private::{Error, FileCredStore};
+
+mod private {
+    use super::*;
+
+    fn serialize_inner<S: Serializer, T: Serialize>(
+        value: &Arc<T>,
+        ser: S,
+    ) -> std::result::Result<S::Ok, S::Error> {
+        value.serialize(ser)
+    }
+
+    fn deserialize_inner<'de, D: Deserializer<'de>, T: Deserialize<'de>>(
+        de: D,
+    ) -> std::result::Result<Arc<T>, D::Error> {
+        let inner: T = Deserialize::deserialize(de)?;
+        Ok(Arc::new(inner))
+    }
+
+    fn serialize_optional_inner<S: Serializer, T: Serialize>(
+        value: &Option<Arc<T>>,
+        ser: S,
+    ) -> std::result::Result<S::Ok, S::Error> {
+        value.as_ref().map(|inner| inner.as_ref()).serialize(ser)
+    }
+
+    fn deserialize_optional_inner<'de, D: Deserializer<'de>, T: Deserialize<'de>>(
+        de: D,
+    ) -> std::result::Result<Option<Arc<T>>, D::Error> {
+        let opt: Option<T> = Deserialize::deserialize(de)?;
+        Ok(opt.map(Arc::new))
+    }
+
+    fn serialize_optional_zeroizing<S: Serializer, T: Serialize + Zeroize>(
+        value: &Option<Zeroizing<T>>,
+        ser: S,
+    ) -> std::result::Result<S::Ok, S::Error> {
+        value
+            .as_ref()
+            .map(|inner| {
+                let reference: &T = inner;
+                reference
+            })
+            .serialize(ser)
+    }
+
+    fn deserialize_optional_zeroizing<'de, D: Deserializer<'de>, T: Deserialize<'de> + Zeroize>(
+        de: D,
+    ) -> std::result::Result<Option<Zeroizing<T>>, D::Error> {
+        let opt: Option<T> = Deserialize::deserialize(de)?;
+        Ok(opt.map(Zeroizing::new))
+    }
+
+    /// Errors which can occur when using a [FileCredStore].
+    #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
+    pub enum Error {
+        /// Indicates that no root credentials were present in a [CredStore].
+        NoRootCreds,
+        /// Indicates that the wrong root password was given when attempting to access the root
+        /// credentials.
+        WrongRootPassword,
+    }
+
+    impl Display for Error {
+        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+            match self {
+                Error::NoRootCreds => write!(f, "root creds are not present"),
+                Error::WrongRootPassword => write!(f, "incorrect root password"),
+            }
+        }
+    }
+
+    impl std::error::Error for Error {}
+
+    #[derive(Serialize, Deserialize)]
+    struct State {
+        #[serde(serialize_with = "serialize_inner")]
+        #[serde(deserialize_with = "deserialize_inner")]
+        /// The credentials of this node.
+        node_creds: Arc<ConcreteCreds>,
+        #[serde(serialize_with = "serialize_optional_inner")]
+        #[serde(deserialize_with = "deserialize_optional_inner")]
+        /// The root credentials of the blocktree this node is part of.
+        root_creds: Option<Arc<ConcreteCreds>>,
+        /// The key used to protect exported root credentials.
+        storage_key: AsymKeyPair<Encrypt>,
+        /// Derivation parameters for turning the root password into a key.
+        derivation_params: DerivationParams,
+        #[serde(serialize_with = "serialize_optional_zeroizing")]
+        #[serde(deserialize_with = "deserialize_optional_zeroizing")]
+        /// The hash generated from the root password.
+        root_password_hash: Option<Zeroizing<[u8; DerivationParams::EXPORT_KEY_KIND.key_len()]>>,
+    }
+
+    impl State {
+        fn new() -> Result<State> {
+            let node_creds = ConcreteCreds::generate()?;
+            let storage_key = Encrypt::RSA_OAEP_3072_SHA_256.generate()?;
+            Ok(State {
+                node_creds: Arc::new(node_creds),
+                root_creds: None,
+                storage_key,
+                derivation_params: DerivationParams::new()?,
+                root_password_hash: None,
+            })
+        }
+
+        fn open_file(file_path: &Path) -> Result<(File, Metadata)> {
+            let result = OpenOptions::new()
+                .read(true)
+                .write(true)
+                .create(false)
+                .open(file_path);
+            let file = match result {
+                Ok(file) => file,
+                Err(err) => {
+                    if io::ErrorKind::NotFound == err.kind() {
+                        OpenOptions::new()
+                            .read(true)
+                            .write(true)
+                            .create_new(true)
+                            .open(file_path)?
+                    } else {
+                        return Err(err.into());
+                    }
+                }
+            };
+            let metadata = file.metadata()?;
+            let mut permissions = metadata.permissions();
+            const MASK: u32 = 0o077;
+            if permissions.mode() & MASK != 0 {
+                permissions.set_mode(permissions.mode() & !MASK);
+                file.set_permissions(permissions)?;
+            }
+            Ok((file, metadata))
+        }
+
+        fn save(&mut self, file_path: &Path) -> Result<()> {
+            let (file, ..) = Self::open_file(file_path)?;
+            let mut writer = BufWriter::new(file);
+            write_to(self, &mut writer)?;
+            Ok(())
+        }
+
+        fn root_password_valid(&self, password: &str) -> Result<()> {
+            let expected = if let Some(ref expected) = self.root_password_hash {
+                expected
+            } else {
+                return Err(Error::NoRootCreds.into());
+            };
+            let actual = self.derivation_params.hmac(password)?;
+            if expected != &actual {
+                return Err(Error::WrongRootPassword.into());
+            }
+            Ok(())
+        }
+
+        fn set_root_creds(&mut self, root_creds: Arc<ConcreteCreds>, password: &str) -> Result<()> {
+            self.root_creds = Some(root_creds);
+            self.root_password_hash = Some(self.derivation_params.hmac(password)?);
+            Ok(())
+        }
+    }
+
+    /// An implementation of [CredStore] which uses a file to store credentials.
+    ///
+    /// This struct relies on the security of the underlying filesystem to protect the
+    /// credentials stored in it. Even with this protection, all processes that run with the same
+    /// UID as the process which creates the credential file will be able to read them.
+    /// In addition, the private keys associated with these credentials
+    /// are loaded into main memory as long as this struct is alive. Thus, this struct does not
+    /// provide anywhere near the level of protection that [crate::crypto::tpm::TpmCredStore] does.
+    pub struct FileCredStore {
+        state: RwLock<State>,
+        file_path: PathBuf,
+    }
+
+    impl FileCredStore {
+        /// Returns a new [FileCredStore] which is stored at the given path.
+        ///
+        /// If no file is present at `file_path`, then a new one will be created containing
+        /// freshly generated credentials.
+        pub fn new(file_path: PathBuf) -> Result<Self> {
+            let (file, metadata) = State::open_file(&file_path)?;
+            let state = if metadata.len() > 0 {
+                let mut reader = BufReader::new(file);
+                read_from::<State, _>(&mut reader)?
+            } else {
+                // If the file is zero length then we must have just created it.
+                let state = State::new()?;
+                let mut writer = BufWriter::new(file);
+                write_to(&state, &mut writer)?;
+                state
+            };
+            Ok(FileCredStore {
+                state: RwLock::new(state),
+                file_path,
+            })
+        }
+    }
+
+    impl CredStore for FileCredStore {
+        type CredHandle = Arc<ConcreteCreds>;
+        type ExportedCreds = TaggedCiphertext<Envelope<ConcreteCreds>, DerivationParams>;
+
+        fn node_creds(&self) -> Result<Self::CredHandle> {
+            let state = self.state.read().display_err()?;
+            Ok(state.node_creds.clone())
+        }
+
+        fn root_creds(&self, password: &str) -> Result<Self::CredHandle> {
+            let state = self.state.read().display_err()?;
+            state.root_password_valid(password)?;
+            let creds = state.root_creds.as_ref().cloned();
+            if let Some(creds) = creds {
+                Ok(creds)
+            } else {
+                Err(Error::NoRootCreds.into())
+            }
+        }
+
+        fn gen_root_creds(&self, password: &str) -> Result<Self::CredHandle> {
+            {
+                let state = self.state.read().display_err()?;
+                if let Some(ref root_creds) = state.root_creds {
+                    state.root_password_valid(password)?;
+                    return Ok(root_creds.clone());
+                }
+            }
+            let mut state = self.state.write().display_err()?;
+            let root_creds = Arc::new(ConcreteCreds::generate()?);
+            state.set_root_creds(root_creds.clone(), password)?;
+            state.save(&self.file_path)?;
+            Ok(root_creds)
+        }
+
+        fn storage_key(&self) -> Result<AsymKeyPub<Encrypt>> {
+            let guard = self.state.read().display_err()?;
+            Ok(guard.storage_key.public.clone())
+        }
+
+        fn export_root_creds(
+            &self,
+            root_creds: &Self::CredHandle,
+            password: &str,
+            new_parent: &AsymKeyPub<Encrypt>,
+        ) -> Result<Self::ExportedCreds> {
+            let envelope = Envelope::new(root_creds.as_ref(), new_parent)?;
+            let params = DerivationParams::new()?;
+            let aead_key = params.derive_key(password)?;
+            let export = aead_key.encrypt(params, &envelope)?;
+            Ok(export)
+        }
+
+        fn import_root_creds(
+            &self,
+            password: &str,
+            exported: Self::ExportedCreds,
+        ) -> Result<Self::CredHandle> {
+            let aead_key = exported.aad.derive_key(password)?;
+            let envelope = aead_key.decrypt(&exported)?;
+            let mut state = self.state.write().display_err()?;
+            let root_creds = Arc::new(envelope.open(&state.storage_key)?);
+            state.set_root_creds(root_creds.clone(), password)?;
+            state.save(&self.file_path)?;
+            Ok(root_creds)
+        }
+
+        fn assign_node_writecap(
+            &self,
+            handle: &mut Self::CredHandle,
+            writecap: crate::Writecap,
+        ) -> Result<()> {
+            let node_creds = Arc::make_mut(handle);
+            node_creds.set_writecap(writecap);
+            let mut state = self.state.write().display_err()?;
+            state.node_creds = handle.clone();
+            state.save(&self.file_path)?;
+            Ok(())
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::{
+        crypto::{Creds, CredsPriv},
+        Epoch, Principaled,
+    };
+
+    use super::*;
+
+    use btserde::to_vec;
+    use std::{ops::Deref, time::Duration};
+    use tempdir::TempDir;
+
+    struct TestCase {
+        store: FileCredStore,
+        dir: TempDir,
+    }
+
+    impl TestCase {
+        fn new() -> TestCase {
+            let dir = TempDir::new("FileCredStore").unwrap();
+            Self::from_dir(dir)
+        }
+
+        fn from_dir(dir: TempDir) -> TestCase {
+            let file_path = Self::file_path(dir.path());
+            let store = FileCredStore::new(file_path).unwrap();
+            TestCase { store, dir }
+        }
+
+        fn file_path(dir_path: &Path) -> PathBuf {
+            let mut file_path = dir_path.to_owned();
+            file_path.push("cred_store");
+            file_path
+        }
+    }
+
+    impl Deref for TestCase {
+        type Target = FileCredStore;
+        fn deref(&self) -> &Self::Target {
+            &self.store
+        }
+    }
+
+    #[test]
+    fn create_new() {
+        let _ = TestCase::new();
+    }
+
+    #[test]
+    fn node_creds() {
+        let case = TestCase::new();
+
+        let result = case.node_creds();
+
+        assert!(result.is_ok());
+    }
+
+    #[test]
+    fn gen_root_creds_and_root_creds() {
+        const PASSWORD: &str = "MaximalIrony";
+        let case = TestCase::new();
+
+        let expected = case.gen_root_creds(PASSWORD).unwrap();
+        let actual = case.root_creds(PASSWORD).unwrap();
+
+        assert!(std::ptr::eq(expected.as_ref(), actual.as_ref()));
+    }
+
+    #[test]
+    fn root_creds_wrong_password_is_error() {
+        let case = TestCase::new();
+
+        case.gen_root_creds("right").unwrap();
+        let result = case.root_creds("wrong");
+
+        let passed = if let Some(err) = result.err() {
+            if let Some(Error::WrongRootPassword) = err.downcast_ref::<Error>() {
+                true
+            } else {
+                false
+            }
+        } else {
+            false
+        };
+        assert!(passed);
+    }
+
+    #[test]
+    fn storage_key() {
+        let case = TestCase::new();
+
+        let result = case.storage_key();
+
+        assert!(result.is_ok());
+    }
+
+    #[test]
+    fn export_import_root_creds() {
+        const SRC_PASSWORD: &str = "FALLING_MAN";
+        const DST_PASSWORD: &str = "RUNNING_MAN";
+        let src = TestCase::new();
+        let dst = TestCase::new();
+
+        let expected = src.gen_root_creds(SRC_PASSWORD).unwrap();
+        let previous = dst.gen_root_creds(DST_PASSWORD).unwrap();
+        let storage_key = dst.storage_key().unwrap();
+        let exported = src
+            .export_root_creds(&expected, SRC_PASSWORD, &storage_key)
+            .unwrap();
+        let actual = dst.import_root_creds(SRC_PASSWORD, exported).unwrap();
+
+        assert!(!std::ptr::eq(previous.as_ref(), actual.as_ref()));
+        assert!(to_vec(expected.as_ref()).unwrap() == to_vec(actual.as_ref()).unwrap());
+    }
+
+    #[test]
+    fn import_root_creds_wrong_password_is_error() {
+        const RIGHT_PW: &str = "right";
+        const WRONG_PW: &str = "wrong";
+        let src = TestCase::new();
+        let dst = TestCase::new();
+
+        let root_creds = src.gen_root_creds("right").unwrap();
+        let storage_key = dst.storage_key().unwrap();
+        let exported = src
+            .export_root_creds(&root_creds, RIGHT_PW, &storage_key)
+            .unwrap();
+        let result = dst.import_root_creds(WRONG_PW, exported);
+
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn assign_node_writecap() {
+        let case = TestCase::new();
+
+        let mut node_creds = case.node_creds().unwrap();
+        let root_creds = case.gen_root_creds("password").unwrap();
+        let expires = Epoch::now() + Duration::from_secs(3600);
+        let expected = root_creds
+            .issue_writecap(node_creds.principal(), vec![], expires)
+            .unwrap();
+        case.assign_node_writecap(&mut node_creds, expected.clone())
+            .unwrap();
+        let actual = node_creds.writecap().unwrap();
+
+        assert_eq!(&expected, actual);
+    }
+
+    fn persistence_test<R: PartialEq, F: Fn(&FileCredStore) -> R>(sample: F) {
+        let case = TestCase::new();
+        let expected = sample(&case);
+
+        let case = TestCase::from_dir(case.dir);
+        let actual = sample(&case);
+
+        assert!(expected == actual);
+    }
+
+    #[test]
+    fn node_creds_persisted() {
+        persistence_test(|store| to_vec(store.node_creds().unwrap().as_ref()).unwrap())
+    }
+
+    #[test]
+    fn root_creds_persisted() {
+        persistence_test(|store| {
+            to_vec(store.gen_root_creds("password").unwrap().as_ref()).unwrap()
+        })
+    }
+
+    #[test]
+    fn storage_key_persisted() {
+        persistence_test(|store| store.storage_key().unwrap());
+    }
+
+    #[test]
+    fn writecap_persisted() {
+        let case = TestCase::new();
+        let mut node_creds = case.node_creds().unwrap();
+        let root_creds = case.gen_root_creds("password").unwrap();
+        let expires = Epoch::now() + Duration::from_secs(3600);
+        let expected = root_creds
+            .issue_writecap(node_creds.principal(), vec![], expires)
+            .unwrap();
+        case.assign_node_writecap(&mut node_creds, expected.clone())
+            .unwrap();
+
+        let case = TestCase::from_dir(case.dir);
+        let node_creds = case.node_creds().unwrap();
+        let actual = node_creds.writecap().unwrap();
+
+        assert_eq!(&expected, actual);
+    }
+}

+ 0 - 42
crates/btlib/src/crypto/tpm.rs

@@ -8,7 +8,6 @@ use log::error;
 use openssl::{
     bn::{BigNum, BigNumRef},
     nid::Nid,
-    pkcs5::pbkdf2_hmac,
 };
 use serde::ser;
 use std::{
@@ -49,7 +48,6 @@ use tss_esapi::{
     Context,
 };
 use tss_esapi_sys::{TPM2_CAP, TPM2_HANDLE, TPM2_MAX_CAP_BUFFER, TPMT_TK_HASHCHECK, TSS2_RC};
-use zeroize::Zeroizing;
 
 impl From<tss_esapi::Error> for Error {
     fn from(err: tss_esapi::Error) -> Self {
@@ -1173,46 +1171,6 @@ impl CredStore for TpmCredStore {
     }
 }
 
-#[derive(Serialize, Deserialize)]
-struct DerivationParams {
-    iter: usize,
-    hash: HashKind,
-    kind: AeadKeyKind,
-    salt: Vec<u8>,
-    iv: Vec<u8>,
-}
-
-impl DerivationParams {
-    const PBKDF2_ITER: usize = 1000000;
-    const PBKDF2_HASH: HashKind = HashKind::Sha2_256;
-    const EXPORT_KEY_KIND: AeadKeyKind = AeadKeyKind::AesGcm256;
-
-    fn new() -> Result<DerivationParams> {
-        const_assert!(
-            DerivationParams::PBKDF2_HASH.len() == DerivationParams::EXPORT_KEY_KIND.key_len()
-        );
-        Ok(DerivationParams {
-            iter: Self::PBKDF2_ITER,
-            hash: Self::PBKDF2_HASH,
-            kind: Self::EXPORT_KEY_KIND,
-            salt: rand_vec(Self::PBKDF2_HASH.len())?,
-            iv: rand_vec(Self::EXPORT_KEY_KIND.iv_len())?,
-        })
-    }
-
-    fn derive_key(&self, password: &str) -> Result<AeadKey> {
-        let mut key = Zeroizing::new([0u8; Self::EXPORT_KEY_KIND.key_len()]);
-        pbkdf2_hmac(
-            password.as_bytes(),
-            self.salt.as_slice(),
-            self.iter,
-            self.hash.into(),
-            key.as_mut_slice(),
-        )?;
-        AeadKey::copy_components(self.kind, key.as_slice(), &self.iv)
-    }
-}
-
 impl<S: Scheme> AsymKeyPub<S> {
     fn try_from(public: Public, scheme: S) -> Result<AsymKeyPub<S>> {
         match public {