Browse Source

Implemented proper support for inherited keys.

Matthew Carr 2 years ago
parent
commit
0bf01c0f75
3 changed files with 156 additions and 61 deletions
  1. 105 55
      crates/btfproto/src/local_fs.rs
  2. 50 6
      crates/btlib/src/lib.rs
  3. 1 0
      crates/btlib/src/test_helpers.rs

+ 105 - 55
crates/btfproto/src/local_fs.rs

@@ -36,6 +36,8 @@ use tokio::sync::{Mutex, OwnedMutexGuard, OwnedRwLockReadGuard, OwnedRwLockWrite
 pub use private::{Authorizer, AuthzContext, Error, LocalFs, ModeAuthorizer};
 
 mod private {
+    use btlib::crypto::SymKey;
+
     use super::*;
 
     type Inode = u64;
@@ -510,8 +512,8 @@ mod private {
         }
 
         /// Decrements the lookup count from the given path by the given amount.
-        fn decr_lookup_count(&mut self, from: &Arc<BlockPath>, decr: u64) {
-            match self.lookup_counts.entry(from.clone()) {
+        fn decr_lookup_count(&mut self, from: Arc<BlockPath>, decr: u64) {
+            match self.lookup_counts.entry(from) {
                 hash_map::Entry::Occupied(mut entry) => {
                     let new_count = entry.get().saturating_sub(decr);
                     if new_count > 0 {
@@ -608,6 +610,7 @@ mod private {
                 SpecInodes::Sb.into(),
                 creds.clone(),
                 root_block_path.clone(),
+                None,
             )?;
             let sb = Superblock {
                 generation,
@@ -631,6 +634,7 @@ mod private {
                 SpecInodes::RootDir.into(),
                 creds.clone(),
                 root_block_path.clone(),
+                None,
             )?;
             write_to(&Directory::new(), &mut root_block)?;
             root_block.mut_meta_body().access_secrets(|secrets| {
@@ -680,6 +684,7 @@ mod private {
                 SpecInodes::Sb.into(),
                 creds.clone(),
                 root_block_path.to_owned(),
+                None,
             )?;
             let sb = read_from(&mut sb_block)?;
             let root_block = Self::open_block(
@@ -687,6 +692,7 @@ mod private {
                 SpecInodes::RootDir.into(),
                 creds.clone(),
                 root_block_path,
+                None,
             )?;
             Self::new(btdir, sb, sb_block, root_block, creds, authorizer)
         }
@@ -737,6 +743,7 @@ mod private {
             inode: Inode,
             creds: C,
             block_path: BlockPath,
+            parent_key: Option<SymKey>,
         ) -> Result<Accessor<FileBlock<C>>> {
             let path = Self::block_path(&btdir, inode);
             let dir = path.ancestors().nth(1).unwrap();
@@ -751,18 +758,20 @@ mod private {
                 .write(true)
                 .create(true)
                 .open(path)?;
-            Self::open_block_file(file, creds, block_path)
+            Self::open_block_file(file, creds, block_path, parent_key)
         }
 
         fn open_block_file(
             file: File,
             creds: C,
             block_path: BlockPath,
+            parent_key: Option<SymKey>,
         ) -> Result<Accessor<FileBlock<C>>> {
             let block = BlockOpenOptions::new()
                 .with_creds(creds)
                 .with_encrypt(true)
                 .with_inner(file)
+                .with_parent_key(parent_key)
                 .with_block_path(block_path)
                 .open()?;
             Ok(block)
@@ -774,12 +783,19 @@ mod private {
 
         async fn open_value(
             &self,
-            from: &Arc<BlockPath>,
+            from: Arc<BlockPath>,
             inode: Inode,
             block_path: BlockPath,
+            parent_key: Option<SymKey>,
         ) -> Result<()> {
-            let block = Self::open_block(&self.path, inode, self.creds.clone(), block_path)?;
-            let value = Arc::new(RwLock::new(InodeTableValue::new(block, from.clone())));
+            let block = Self::open_block(
+                &self.path,
+                inode,
+                self.creds.clone(),
+                block_path,
+                parent_key,
+            )?;
+            let value = Arc::new(RwLock::new(InodeTableValue::new(block, from)));
             let mut inodes = self.inodes.write().await;
             if inodes.insert(inode, value).is_some() {
                 error!(
@@ -789,27 +805,33 @@ mod private {
             Ok(())
         }
 
+        /// Ensures that the given inode is open. If the inode is already open, then this method
+        /// does nothing and returns the table guard which was used to check the status of the
+        /// inode.
+        /// ## Warning
+        /// Because this method creates new table guards, no table guard must be alive when it's
+        /// called. Otherwise a deadlock will occur.
         async fn ensure_open<'a>(
             &'a self,
             from: &Arc<BlockPath>,
             inode: Inode,
             block_path: BlockPath,
+            parent_key: Option<SymKey>,
         ) -> Result<TableGuard<C>> {
             {
-                let inodes = self.inodes.clone().read_owned().await;
-                if inodes.contains_key(&inode) {
-                    return Ok(TableGuard {
-                        table_guard: inodes,
-                    });
+                let table_guard = self.inodes.clone().read_owned().await;
+                if table_guard.contains_key(&inode) {
+                    return Ok(TableGuard { table_guard });
                 }
             }
-            self.open_value(from, inode, block_path).await?;
+            self.open_value(from.clone(), inode, block_path, parent_key)
+                .await?;
             Ok(TableGuard::new(self.inodes.clone()).await)
         }
 
         async fn inode_forget<'a>(
             &self,
-            from: &Arc<BlockPath>,
+            from: Arc<BlockPath>,
             inode: Inode,
             count: u64,
         ) -> io::Result<()> {
@@ -880,7 +902,7 @@ mod private {
 
         /// Grants the given credentials access to the directory this instance is responsible for.
         ///
-        /// # Warning
+        /// ## Warning
         /// This method calls `self.authz_attrs`, so the same consideration for avoiding deadlock
         /// apply to this method as well. See the documentation of `self.authz_attrs` for details.
         async fn grant_access_to(
@@ -891,12 +913,12 @@ mod private {
         ) -> Result<()> {
             let authz_attrs = self.authz_attrs(from).await?;
             let principal = proc_rec.pub_creds.principal();
-            let next_inode = {
+            let (next_inode, parent_key) = {
                 let table_guard = self.table_guard().await;
 
                 let next_inode = if inode == SpecInodes::RootDir.value() {
                     // If the inode is for the root directory we need to add a readcap for the
-                    // superblock when we access it.
+                    // superblock.
                     let mut value_guard = table_guard.write(SpecInodes::Sb.into()).await?;
                     let mut block = &mut value_guard.block;
                     let next_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
@@ -916,7 +938,7 @@ mod private {
                     self.next_inode().await
                 }?;
 
-                {
+                let parent_key = {
                     let mut value_guard = table_guard.write(inode).await?;
                     let block = &mut value_guard.block;
                     self.authorizer.can_write(&AuthzContext::new(
@@ -934,37 +956,40 @@ mod private {
                     // Note that write_dir calls flush, which also ensures metadata is written to
                     // disk.
                     block.write_dir(&dir)?;
-                }
 
-                next_inode
+                    block.meta_body().block_key()?.clone()
+                };
+
+                (next_inode, parent_key)
             };
 
             let self_writecap = self.creds.writecap().ok_or(BlockError::MissingWritecap)?;
             let self_bind_path = Arc::new(self_writecap.bind_path());
             let bind_path = proc_rec.writecap.bind_path();
-            self.open_value(&self_bind_path, next_inode, bind_path)
-                .await?;
+            self.open_value(
+                self_bind_path.clone(),
+                next_inode,
+                bind_path,
+                Some(parent_key),
+            )
+            .await?;
             {
                 let table_guard = self.table_guard().await;
                 let mut value_guard = table_guard.write(next_inode).await?;
                 let block = &mut value_guard.block;
-                // TODO: Adding a readcap should not be necessary because the principal has been
-                // given access to the root directory.
-                // Once proper inherited readcaps are implemented we will
-                // no longer need to add a readcap to this block.
-                block
-                    .mut_meta_body()
-                    .add_readcap_for(principal, &proc_rec.pub_creds.enc)?;
                 block.write_proc_rec(&ProcRec::Valid(proc_rec))?;
             };
             // We must ensure the reference count for the inode is decremented, otherwise the table
             // entry will never be freed.
-            self.inode_forget(&self_bind_path, next_inode, 1).await?;
+            self.inode_forget(self_bind_path, next_inode, 1).await?;
             Ok(())
         }
 
-        async fn lookup_inode_in(&self, parent: Inode, name: &str) -> Result<Inode> {
-            let table_guard = self.table_guard().await;
+        async fn lookup_inode_in(
+            table_guard: &TableGuard<C>,
+            parent: Inode,
+            name: &str,
+        ) -> Result<Inode> {
             let mut value_guard = table_guard.write(parent).await?;
             let dir = value_guard.block.read_dir()?;
             dir.entry(name)
@@ -972,20 +997,20 @@ mod private {
                 .map(|e| e.inode())
         }
 
+        /// Returns a pair of inodes, where the first inode is the inode referred to by the given
+        /// path, and the second is the parent inode.
         async fn lookup_inode<'a, I: Iterator<Item = &'a str>>(
-            &self,
+            table_guard: &TableGuard<C>,
             components: I,
-        ) -> Result<Inode> {
+        ) -> Result<(Inode, Option<Inode>)> {
             const ROOT: Inode = SpecInodes::RootDir as Inode;
+            let mut parent = None;
             let mut inode = ROOT;
             for component in components {
-                inode = self.lookup_inode_in(inode, component).await?;
-            }
-            if inode == ROOT {
-                Err(io::Error::from_raw_os_error(libc::ENOENT).into())
-            } else {
-                Ok(inode)
+                parent = Some(inode);
+                inode = Self::lookup_inode_in(table_guard, inode, component).await?;
             }
+            Ok((inode, parent))
         }
 
         /// Retrieves the authorization attributes for the principal identified by the given path.
@@ -1011,10 +1036,21 @@ mod private {
             }
             let local_root = writecap.path();
             let relative = from.relative_to(local_root)?;
-            let inode = self.lookup_inode(relative.components()).await?;
+            let (inode, parent_key) = {
+                let table_guard = self.table_guard().await;
+                let (inode, parent) =
+                    Self::lookup_inode(&table_guard, relative.components()).await?;
+                let parent_key = if let Some(parent) = parent {
+                    let value_guard = table_guard.read(parent).await?;
+                    Some(value_guard.block.meta_body().block_key()?.clone())
+                } else {
+                    None
+                };
+                (inode, parent_key)
+            };
             let proc_rec = {
                 let table_guard = self
-                    .ensure_open(from, inode, from.as_ref().to_owned())
+                    .ensure_open(from, inode, from.as_ref().to_owned(), parent_key)
                     .await?;
                 let mut value_guard = table_guard.write(inode).await?;
                 value_guard.block.read_proc_rec()?
@@ -1109,18 +1145,20 @@ mod private {
                 debug!("lookup: parent {parent}, {:?}", name);
                 let authz_attrs = self.authz_attrs(from).await?;
 
-                let (dir, block_path) = {
+                let (dir, block_path, parent_key) = {
                     let table_guard = self.table_guard().await;
-                    let mut parent_value = table_guard.write(parent).await?;
-                    let parent_block = &mut parent_value.block;
+                    let mut value_guard = table_guard.write(parent).await?;
+                    let parent_block = &mut value_guard.block;
                     self.authorizer.can_exec(&AuthzContext::new(
                         from,
                         &authz_attrs,
                         parent_block.meta(),
                     ))?;
                     let dir = parent_block.read_dir()?;
-                    let block_path = parent_block.meta_body().path().to_owned();
-                    (dir, block_path)
+                    let meta_body = parent_block.meta_body();
+                    let block_path = meta_body.path().to_owned();
+                    let parent_key = meta_body.block_key()?.clone();
+                    (dir, block_path, parent_key)
                 };
 
                 let entry = dir
@@ -1128,7 +1166,9 @@ mod private {
                     .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
                 let inode = entry.inode();
                 let stat = {
-                    let table_guard = self.ensure_open(from, inode, block_path).await?;
+                    let table_guard = self
+                        .ensure_open(from, inode, block_path, Some(parent_key))
+                        .await?;
                     let mut value_guard = table_guard.write(inode).await?;
                     let stat = value_guard.block.meta_body().secrets()?.to_owned();
                     value_guard.incr_lookup_count(from);
@@ -1160,7 +1200,7 @@ mod private {
                 let name = msg.name.to_owned();
 
                 // Add a directory entry to the parent for the new inode.
-                let (inode, mut block_path) = {
+                let (inode, mut block_path, parent_key) = {
                     let table_guard = self.table_guard().await;
                     let mut value_guard = table_guard.write(parent).await?;
                     let block = &mut value_guard.block;
@@ -1181,12 +1221,18 @@ mod private {
                     dir.add_file(name.clone(), inode)?;
                     block.write_dir(&dir)?;
 
-                    (inode, block.meta_body().path().clone())
+                    let meta_body = block.meta_body();
+                    let block_path = meta_body.path().clone();
+                    let parent_key = meta_body.block_key()?.clone();
+
+                    (inode, block_path, parent_key)
                 };
                 block_path.push_component(name);
 
                 let (handle, stat) = {
-                    let table_guard = self.ensure_open(from, inode, block_path).await?;
+                    let table_guard = self
+                        .ensure_open(from, inode, block_path, Some(parent_key))
+                        .await?;
                     let mut value_guard = table_guard.write(inode).await?;
                     let handle =
                         value_guard.new_handle(from.clone(), FlagValue::ReadWrite.into())?;
@@ -1444,7 +1490,7 @@ mod private {
                 let Unlink { parent, name } = msg;
                 debug!("unlink: parent {parent}, name {name}");
                 let authz_attrs = self.authz_attrs(from).await?;
-                let (block_path, inode) = {
+                let (block_path, inode, parent_key) = {
                     let table_guard = self.table_guard().await;
                     let mut value_guard = table_guard.write(parent).await?;
                     let parent_block = &mut value_guard.block;
@@ -1463,12 +1509,16 @@ mod private {
                     let inode = entry.inode();
                     parent_block.write_dir(&dir)?;
 
-                    let mut block_path = parent_block.meta_body().path().clone();
+                    let meta_body = parent_block.meta_body();
+                    let mut block_path = meta_body.path().clone();
                     block_path.push_component(name.to_owned());
-                    (block_path, inode)
+                    let parent_key = meta_body.block_key()?.clone();
+                    (block_path, inode, parent_key)
                 };
 
-                let table_guard = self.ensure_open(from, inode, block_path).await?;
+                let table_guard = self
+                    .ensure_open(from, inode, block_path, Some(parent_key))
+                    .await?;
                 let mut value = table_guard.write(inode).await?;
                 // We mark the block for deletion if `nlink` drops to zero.
                 let block = value.block_mut();
@@ -1637,7 +1687,7 @@ mod private {
             async move {
                 let Forget { inode, count } = msg;
                 debug!("forget: inode {inode}, count {count}");
-                self.inode_forget(from, inode, count).await.bterr()
+                self.inode_forget(from.clone(), inode, count).await.bterr()
             }
         }
 

+ 50 - 6
crates/btlib/src/lib.rs

@@ -59,6 +59,7 @@ pub enum BlockError {
     UnknownSize,
     ProcRecNotIssued,
     ProcRecRevoked,
+    NoInheritedKey,
 }
 
 impl Display for BlockError {
@@ -75,6 +76,7 @@ impl Display for BlockError {
                 write!(f, "a process record was requested by not yet issued")
             }
             BlockError::ProcRecRevoked => write!(f, "this process record has been revoked"),
+            BlockError::NoInheritedKey => write!(f, "block metadata has no inherited key"),
         }
     }
 }
@@ -717,6 +719,21 @@ impl BlockMetaBody {
         Ok(())
     }
 
+    /// Uses the given symmetric key to decrypt the `inherit` field. If this field is `None`, or if
+    /// the decryption fails, then an error is returned. If the block key has already been decrypted
+    /// then this method does nothing.
+    pub fn unlock_block_key_with_parent_key(&mut self, parent_key: SymKey) -> Result<()> {
+        if self.block_key.is_some() {
+            return Ok(());
+        }
+        if let Some(ref inherit) = self.inherit {
+            self.block_key = Some(parent_key.ser_decrypt(inherit)?);
+            Ok(())
+        } else {
+            Err(BlockError::NoInheritedKey.into())
+        }
+    }
+
     pub fn access_secrets<T, F: FnOnce(&mut BlockMetaSecrets) -> Result<T>>(
         &mut self,
         accessor: F,
@@ -832,13 +849,22 @@ pub struct BlockStream<T, C> {
 }
 
 impl<T: ReadAt + Sectored + Size, C: Creds> BlockStream<T, C> {
-    fn new(inner: T, creds: C, block_path: BlockPath) -> Result<BlockStream<T, C>> {
+    fn new(
+        inner: T,
+        creds: C,
+        parent_key: Option<SymKey>,
+        block_path: BlockPath,
+    ) -> Result<BlockStream<T, C>> {
         let (trailered, meta) = Trailered::<_, BlockMeta>::new(inner)?;
         let meta = match meta {
             Some(mut meta) => {
                 meta.assert_valid(&block_path)?;
                 meta.body.path = block_path;
-                meta.body.use_readcap_for(&creds)?;
+                if let Some(parent_key) = parent_key {
+                    meta.body.unlock_block_key_with_parent_key(parent_key)?;
+                } else {
+                    meta.body.use_readcap_for(&creds)?;
+                }
                 // We need to use the writecap and signing_key provided by the current credentials.
                 meta.body.writecap = Some(
                     creds
@@ -853,7 +879,11 @@ impl<T: ReadAt + Sectored + Size, C: Creds> BlockStream<T, C> {
             None => {
                 let mut meta = BlockMeta::new(&creds)?;
                 meta.body.path = block_path;
-                meta.body.add_readcap_for(creds.principal(), &creds)?;
+                if let Some(parent_key) = parent_key {
+                    meta.body.inherit = Some(parent_key.ser_encrypt(meta.body.block_key()?)?);
+                } else {
+                    meta.body.add_readcap_for(creds.principal(), &creds)?;
+                }
                 meta.body.writecap = Some(
                     creds
                         .writecap()
@@ -954,6 +984,7 @@ pub struct BlockOpenOptions<T, C> {
     creds: C,
     encrypt: bool,
     block_path: Option<BlockPath>,
+    parent_key: Option<SymKey>,
 }
 
 impl BlockOpenOptions<(), ()> {
@@ -963,6 +994,7 @@ impl BlockOpenOptions<(), ()> {
             creds: (),
             encrypt: true,
             block_path: Default::default(),
+            parent_key: None,
         }
     }
 }
@@ -974,6 +1006,7 @@ impl<T, C> BlockOpenOptions<T, C> {
             creds: self.creds,
             encrypt: self.encrypt,
             block_path: self.block_path,
+            parent_key: self.parent_key,
         }
     }
 
@@ -983,6 +1016,7 @@ impl<T, C> BlockOpenOptions<T, C> {
             creds,
             encrypt: self.encrypt,
             block_path: self.block_path,
+            parent_key: self.parent_key,
         }
     }
 
@@ -995,6 +1029,11 @@ impl<T, C> BlockOpenOptions<T, C> {
         self.block_path = Some(block_path);
         self
     }
+
+    pub fn with_parent_key(mut self, parent_key: Option<SymKey>) -> Self {
+        self.parent_key = parent_key;
+        self
+    }
 }
 
 pub type ConcreteBlock<T, C> = MerkleStream<BlockStream<T, C>>;
@@ -1003,7 +1042,7 @@ pub type FileBlock<C> = ConcreteBlock<std::fs::File, C>;
 impl<T: ReadAt + WriteAt + Size + Sectored + 'static, C: Creds + 'static> BlockOpenOptions<T, C> {
     pub fn open_bare(self) -> Result<ConcreteBlock<T, C>> {
         let block_path = self.block_path.ok_or(BlockError::NoBlockPath)?;
-        let stream = BlockStream::new(self.inner, self.creds, block_path)?;
+        let stream = BlockStream::new(self.inner, self.creds, self.parent_key, block_path)?;
         let mut stream = MerkleStream::new(stream)?;
         stream.assert_root_integrity()?;
         Ok(stream)
@@ -1512,8 +1551,13 @@ mod tests {
 
         fn stream(&self, vec: Vec<u8>) -> EncBlock {
             let inner = SectoredCursor::new(vec, SECTOR_SZ_DEFAULT).require_sect_sz(false);
-            let mut stream =
-                BlockStream::new(inner, self.node_creds.clone(), self.block_path.clone()).unwrap();
+            let mut stream = BlockStream::new(
+                inner,
+                self.node_creds.clone(),
+                None,
+                self.block_path.clone(),
+            )
+            .unwrap();
             stream
                 .mut_meta_body()
                 .access_secrets(|secrets| {

+ 1 - 0
crates/btlib/src/test_helpers.rs

@@ -225,6 +225,7 @@ pub fn make_block_with<C: CredsPub>(creds: &C) -> SectoredBuf<SecretStream<PioCu
     let mut stream = BlockStream::new(
         SectoredCursor::new(Vec::new(), SECTOR_SZ_DEFAULT).require_sect_sz(false),
         creds,
+        None,
         path,
     )
     .expect("create block stream failed");