Răsfoiți Sursa

Made it more difficult to launch a correlation attack against the
readcap dictionary.

Matthew Carr 2 ani în urmă
părinte
comite
54dbf52dd5

+ 3 - 9
crates/btfproto/src/client.rs

@@ -1,11 +1,7 @@
 // SPDX-License-Identifier: AGPL-3.0-or-later
 use crate::{msg::*, Handle, Inode};
 
-use btlib::{
-    bterr,
-    crypto::{AsymKeyPub, Encrypt},
-    IssuedProcRec, Principal, Result,
-};
+use btlib::{bterr, crypto::ConcretePub, IssuedProcRec, Result};
 use btmsg::{DeserCallback, Transmitter};
 
 use core::future::{ready, Future, Ready};
@@ -305,14 +301,12 @@ impl<T: Transmitter> FsClient<T> {
         &self,
         inode: Inode,
         handle: Handle,
-        principal: Principal,
-        enc_key: AsymKeyPub<Encrypt>,
+        pub_creds: ConcretePub,
     ) -> Result<()> {
         let msg = FsMsg::AddReadcap(AddReadcap {
             inode,
             handle,
-            principal,
-            enc_key,
+            pub_creds,
         });
         self.tx.call(msg, AckCallback).await?
     }

+ 5 - 10
crates/btfproto/src/local_fs.rs

@@ -1041,9 +1041,7 @@ mod private {
                     let next_inode = self.sb.next_inode.fetch_add(1, Ordering::Relaxed);
                     block.rewind()?;
                     write_to(&self.sb, &mut block)?;
-                    block
-                        .mut_meta_body()
-                        .add_readcap_for(principal.clone(), &proc_rec.pub_creds.enc)?;
+                    block.mut_meta_body().add_readcap_for(&proc_rec.pub_creds)?;
                     block.flush()?;
 
                     Ok(next_inode)
@@ -1059,9 +1057,7 @@ mod private {
                         &authz_attrs,
                         block.meta(),
                     ))?;
-                    block
-                        .mut_meta_body()
-                        .add_readcap_for(principal.clone(), &proc_rec.pub_creds.enc)?;
+                    block.mut_meta_body().add_readcap_for(&proc_rec.pub_creds)?;
 
                     let mut dir = block.read_dir()?;
                     let proc_rec_name = principal.to_string();
@@ -1868,14 +1864,13 @@ mod private {
                 let AddReadcap {
                     inode,
                     handle,
-                    principal,
-                    enc_key,
+                    pub_creds,
                 } = msg;
-                debug!("add_readcap: inode {inode}, handle {handle}, principal {principal}");
+                debug!("add_readcap: inode {inode}, handle {handle}");
                 let table_guard = self.table_guard().await;
                 let mut value_guard = table_guard.write(inode).await?;
                 let mut block = value_guard.handle_guard_mut(from, handle).await?;
-                block.mut_meta_body().add_readcap_for(principal, &enc_key)
+                block.mut_meta_body().add_readcap_for(pub_creds)
             }
         }
 

+ 2 - 5
crates/btfproto/src/msg.rs

@@ -2,9 +2,7 @@
 use super::{Handle, Inode};
 
 use btlib::{
-    bterr,
-    crypto::{AsymKeyPub, Encrypt},
-    BlockMetaSecrets, DirEntry, DirEntryKind, Epoch, IssuedProcRec, Principal,
+    bterr, crypto::ConcretePub, BlockMetaSecrets, DirEntry, DirEntryKind, Epoch, IssuedProcRec,
 };
 use btmsg::CallMsg;
 use core::time::Duration;
@@ -589,8 +587,7 @@ pub struct Unlock {
 pub struct AddReadcap {
     pub inode: Inode,
     pub handle: Handle,
-    pub principal: Principal,
-    pub enc_key: AsymKeyPub<Encrypt>,
+    pub pub_creds: ConcretePub,
 }
 
 #[derive(Serialize, Deserialize)]

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

@@ -619,7 +619,7 @@ mod tests {
             .await
             .unwrap();
         client
-            .add_readcap(inode, handle, creds.principal(), creds.concrete_pub().enc)
+            .add_readcap(inode, handle, creds.concrete_pub())
             .await
             .unwrap();
     }

+ 2 - 5
crates/btlib/src/accessor.rs

@@ -158,14 +158,11 @@ mod private {
 #[cfg(test)]
 mod test {
     use super::*;
-    use crate::test_helpers::{make_block_with, node_creds};
+    use crate::test_helpers::make_block_with;
 
     #[test]
     fn can_wrap_block_ref() {
-        let block = make_block_with(node_creds())
-            .into_inner()
-            .into_inner()
-            .into_inner();
+        let block = make_block_with().into_inner().into_inner().into_inner();
         let mut accessor = Accessor::new(block).expect("failed to wrap block");
         const EXPECTED: &[u8] = &[1u8; 8];
 

+ 1 - 2
crates/btlib/src/crypto.rs

@@ -2310,8 +2310,7 @@ mod tests {
     fn encrypt_decrypt_block() {
         const SECT_SZ: usize = 16;
         const SECT_CT: usize = 8;
-        let creds = make_key_pair();
-        let mut block = make_block_with(&creds);
+        let mut block = make_block_with();
         write_fill(&mut block, SECT_SZ, SECT_CT);
         block.rewind().expect("rewind failed");
         read_check(block, SECT_SZ, SECT_CT);

+ 24 - 34
crates/btlib/src/lib.rs

@@ -9,10 +9,12 @@ pub mod crypto;
 pub mod drop_trigger;
 pub mod error;
 pub mod log;
+mod readcap_dict;
 pub mod sectored_buf;
 #[cfg(test)]
 mod test_helpers;
 mod trailered;
+use readcap_dict::ReadcapDict;
 
 #[macro_use]
 extern crate static_assertions;
@@ -43,7 +45,7 @@ use strum_macros::{Display, EnumDiscriminants, FromRepr};
 use accessor::Accessor;
 pub use block_path::{BlockPath, BlockPathError};
 use crypto::{
-    AsymKeyPub, Ciphertext, ConcretePub, Creds, Decrypter, DecrypterExt, Encrypter, EncrypterExt,
+    AsymKeyPub, Ciphertext, ConcretePub, Creds, CredsPub, Decrypter, DecrypterExt, EncrypterExt,
     HashKind, MerkleStream, SecretStream, Sign, Signature, Signer, SymKey, SymKeyKind, VarHash,
 };
 use error::{BoxInIoErr, BtErr};
@@ -667,7 +669,7 @@ pub struct BlockMetaBody {
     /// A copy of the block key encrypted using this block's parent's key. If this is None, then
     /// this block is not encrypted.
     inherit: Option<Ciphertext<SymKey>>,
-    readcaps: BTreeMap<Principal, Ciphertext<SymKey>>,
+    readcaps: ReadcapDict,
     writecap: Option<Writecap>,
     /// A hash which provides integrity for the contents of the block body.
     integrity: Option<VarHash>,
@@ -689,34 +691,22 @@ pub struct BlockMetaBody {
 }
 
 impl BlockMetaBody {
-    fn new<C: Creds>(creds: &C) -> Result<BlockMetaBody> {
+    fn new<C: Creds>(creds: C) -> Result<BlockMetaBody> {
         let block_key = SymKey::generate(SymKeyKind::default())?;
-        let mut readcaps = BTreeMap::new();
-        readcaps.insert(creds.principal(), creds.ser_encrypt(&block_key)?);
         let secrets = BlockMetaSecrets::default();
-        Ok(BlockMetaBody {
+        let mut body = BlockMetaBody {
             path: BlockPath::default(),
             inherit: None,
-            readcaps,
+            readcaps: ReadcapDict::new()?,
             writecap: creds.writecap().map(|e| e.to_owned()),
             integrity: None,
             signing_key: creds.public_sign().to_owned(),
             secrets: block_key.ser_encrypt(&secrets)?,
             block_key: Some(block_key),
             secrets_struct: Some(secrets),
-        })
-    }
-
-    pub fn unlock_block_key<C: Decrypter + Principaled>(&mut self, creds: &C) -> Result<()> {
-        if self.block_key.is_some() {
-            return Ok(());
-        }
-        let readcap = self
-            .readcaps
-            .get(&creds.principal())
-            .ok_or(crypto::Error::NoReadCap)?;
-        self.block_key = Some(creds.ser_decrypt(readcap)?);
-        Ok(())
+        };
+        body.add_readcap_for(creds)?;
+        Ok(body)
     }
 
     /// Uses the given symmetric key to decrypt the `inherit` field. If this field is `None`, or if
@@ -769,6 +759,10 @@ impl BlockMetaBody {
             .ok_or_else(|| bterr!(BlockError::NoBlockKey))
     }
 
+    pub fn mut_block_key(&mut self) -> &mut Option<SymKey> {
+        &mut self.block_key
+    }
+
     pub fn block_id(&self) -> Result<&BlockId> {
         let secrets = self.secrets()?;
         Ok(secrets.block_id())
@@ -779,21 +773,17 @@ impl BlockMetaBody {
     }
 
     pub fn use_readcap_for<C: Creds>(&mut self, creds: &C) -> Result<&SymKey> {
-        let owner = creds.principal();
-        let readcap = self.readcaps.get(&owner).ok_or(crypto::Error::NoReadCap)?;
-        let block_key = creds.ser_decrypt(readcap)?;
+        let block_key = self.readcaps.get(creds)?;
         self.block_key = Some(block_key);
         self.block_key()
     }
 
-    pub fn mut_block_key(&mut self) -> &mut Option<SymKey> {
-        &mut self.block_key
-    }
-
-    pub fn add_readcap_for<E: Encrypter>(&mut self, owner: Principal, key: &E) -> Result<()> {
-        let block_key = self.block_key()?;
-        self.readcaps.insert(owner, key.ser_encrypt(block_key)?);
-        Ok(())
+    pub fn add_readcap_for<C: CredsPub>(&mut self, creds: C) -> Result<()> {
+        let block_key = self
+            .block_key
+            .as_ref()
+            .ok_or_else(|| bterr!(BlockError::NoBlockKey))?;
+        self.readcaps.set(creds, block_key)
     }
 
     pub fn path(&self) -> &BlockPath {
@@ -882,7 +872,7 @@ impl<T: ReadAt + Sectored + Size, C: Creds> BlockStream<T, C> {
                 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.add_readcap_for(&creds)?;
                 }
                 meta.body.writecap = Some(
                     creds
@@ -1507,7 +1497,7 @@ mod tests {
         };
 
         let mut body = from_vec::<BlockMetaBody>(&vec).expect("from_vec failed");
-        body.unlock_block_key(&creds)
+        body.use_readcap_for(&creds)
             .expect("unlock_block_key failed");
         let actual_uid = body
             .access_secrets(|secrets| Ok(secrets.uid))
@@ -1779,7 +1769,7 @@ mod tests {
             let mut block = case.open_new(path.clone());
             block
                 .mut_meta_body()
-                .add_readcap_for(app_creds.principal(), &app_creds)
+                .add_readcap_for(&app_creds)
                 .expect("failed to add readcap");
             block.write(&EXPECTED[..MID]).expect("first write failed");
             block.flush().expect("first flush failed");

+ 139 - 0
crates/btlib/src/readcap_dict.rs

@@ -0,0 +1,139 @@
+//! This module contains the [ReadcapDict] type.
+
+use crate::{
+    crypto::{
+        self, rand_vec, Ciphertext, Creds, CredsPub, DecrypterExt, EncrypterExt, HashKind, SymKey,
+    },
+    Result,
+};
+use serde::{Deserialize, Serialize};
+use std::collections::BTreeMap;
+
+pub use private::ReadcapDict;
+
+mod private {
+    use super::*;
+
+    /// A container for the readcaps stored in a block's metadata. This type is responsible for
+    /// obscuring the owner of the readcaps its stores.
+    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
+    pub struct ReadcapDict {
+        /// The message digest to use for computing principals from public keys, and to digest
+        /// these principals with the salt.
+        kind: HashKind,
+        /// The salt used to obscure the owner of each readcap.
+        salt: Vec<u8>,
+        readcaps: BTreeMap<Vec<u8>, Ciphertext<SymKey>>,
+    }
+
+    impl ReadcapDict {
+        pub fn new() -> Result<Self> {
+            let kind = HashKind::default();
+            Ok(ReadcapDict {
+                kind,
+                salt: rand_vec(kind.len())?,
+                readcaps: BTreeMap::new(),
+            })
+        }
+
+        /// Computes the key to use for the given credentials. This function should be considered
+        /// security critical, as the readcap map is stored in plaintext, so an attacker can attempt
+        /// to correlate the the readcaps from different blocks.
+        fn key<C: CredsPub>(&self, creds: C) -> Result<Vec<u8>> {
+            let principal = creds.principal_of_kind(self.kind);
+            let mut key = vec![0u8; self.kind.len()];
+            self.kind
+                .digest(&mut key, [principal.as_slice(), &self.salt].into_iter())?;
+            Ok(key)
+        }
+
+        pub fn get<C: Creds>(&self, creds: C) -> Result<SymKey> {
+            let key = self.key(&creds)?;
+            let value = self.readcaps.get(&key).ok_or(crypto::Error::NoReadCap)?;
+            creds.ser_decrypt(value)
+        }
+
+        pub fn set<C: CredsPub>(&mut self, creds: C, block_key: &SymKey) -> Result<()> {
+            let key = self.key(&creds)?;
+            let value = creds.ser_encrypt(block_key)?;
+            self.readcaps.insert(key, value);
+            Ok(())
+        }
+
+        /// Attempts to remove the readcap for the given creds from this dictionary. When the `Ok`
+        /// variant is returned, it contains `true` if the creds were present prior to removal,
+        /// and `false` otherwise.
+        #[allow(dead_code)]
+        pub fn remove<C: CredsPub>(&mut self, creds: C) -> Result<bool> {
+            let key = self.key(creds)?;
+            Ok(self.readcaps.remove(&key).is_some())
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use crate::{crypto::SymKeyKind, test_helpers::node_creds};
+
+    #[test]
+    fn set_then_get() {
+        let mut dict = ReadcapDict::new().unwrap();
+        let creds = node_creds();
+        let expected = SymKey::generate(SymKeyKind::Aes256Ctr).unwrap();
+
+        dict.set(creds, &expected).unwrap();
+        let actual = dict.get(creds).unwrap();
+
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn get_returns_no_readcap_when_key_not_found() {
+        let dict = ReadcapDict::new().unwrap();
+
+        let result = dict.get(node_creds());
+
+        let err = result.err().unwrap().downcast::<crypto::Error>().unwrap();
+        let passed = if let crypto::Error::NoReadCap = err {
+            true
+        } else {
+            false
+        };
+        assert!(passed);
+    }
+
+    #[test]
+    fn remove_after_set_returns_true() {
+        let mut dict = ReadcapDict::new().unwrap();
+        let creds = node_creds();
+        let sym_key = SymKey::generate(SymKeyKind::Aes256Cbc).unwrap();
+
+        dict.set(creds, &sym_key).unwrap();
+        let was_present = dict.remove(creds).unwrap();
+        let get_result = dict.get(creds);
+
+        assert!(was_present);
+        let err = get_result
+            .err()
+            .unwrap()
+            .downcast::<crypto::Error>()
+            .unwrap();
+        let passed = if let crypto::Error::NoReadCap = err {
+            true
+        } else {
+            false
+        };
+        assert!(passed);
+    }
+
+    #[test]
+    fn remove_when_not_set_returns_false() {
+        let mut dict = ReadcapDict::new().unwrap();
+
+        let was_present = dict.remove(node_creds()).unwrap();
+
+        assert!(!was_present);
+    }
+}

+ 2 - 15
crates/btlib/src/test_helpers.rs

@@ -200,27 +200,14 @@ pub fn make_self_signed_writecap_with<C: Creds>(key: &C) -> Writecap {
     writecap
 }
 
-pub fn make_block_with<C: CredsPub>(creds: &C) -> SectoredBuf<SecretStream<PioCursor<impl Block>>> {
+pub fn make_block_with() -> SectoredBuf<SecretStream<PioCursor<impl Block>>> {
     let block_key = SymKey::generate(SymKeyKind::default()).unwrap();
-    let mut readcaps = BTreeMap::new();
-    readcaps.insert(creds.principal(), creds.ser_encrypt(&block_key).unwrap());
     // Notice that the writecap path contains the block path. If this were not the case, the block
     // would be invalid.
     let (writecap, creds) = make_writecap_and_creds(vec!["apps"]);
     let root_writecap = writecap.next.as_ref().unwrap();
     let path = make_path_with_root(root_writecap.body.issued_to.clone(), vec!["apps", "verse"]);
-    let secrets = BlockMetaSecrets::default();
-    let header = BlockMetaBody {
-        path: path.clone(),
-        inherit: Some(INHERIT.clone()),
-        readcaps,
-        writecap: Some(writecap),
-        integrity: Some(VarHash::from(HashKind::Sha2_256)),
-        signing_key: creds.public_sign().to_owned(),
-        secrets: block_key.ser_encrypt(&secrets).unwrap(),
-        block_key: Some(block_key.clone()),
-        secrets_struct: Some(secrets),
-    };
+    let header = BlockMetaBody::new(&creds).unwrap();
     let sig = Signature::copy_from(Sign::RSA_PSS_3072_SHA_256, &SIGNATURE);
     let mut stream = BlockStream::new(
         SectoredCursor::new(Vec::new(), SECTOR_SZ_DEFAULT).require_sect_sz(false),