|
@@ -26,16 +26,19 @@ use crypto::{
|
|
|
MerkleStream, PublicCreds, SecretStream, Sign, Signature, Signer, SymKey, SymKeyKind, VarHash,
|
|
|
};
|
|
|
|
|
|
-use fuse_backend_rs::file_traits::FileReadWriteVolatile;
|
|
|
+use fuse_backend_rs::{
|
|
|
+ abi::fuse_abi::{stat64, Attr},
|
|
|
+ file_traits::FileReadWriteVolatile,
|
|
|
+};
|
|
|
use log::error;
|
|
|
use sectored_buf::SectoredBuf;
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
use serde_big_array::BigArray;
|
|
|
use std::{
|
|
|
- collections::{btree_map, BTreeMap, HashMap},
|
|
|
+ collections::{btree_map, hash_map::DefaultHasher, BTreeMap, HashMap},
|
|
|
convert::{Infallible, TryFrom},
|
|
|
fmt::{self, Display, Formatter},
|
|
|
- hash::Hash as Hashable,
|
|
|
+ hash::{Hash as Hashable, Hasher},
|
|
|
io::{self, Read, Seek, SeekFrom, Write},
|
|
|
net::SocketAddr,
|
|
|
ops::{Add, Sub},
|
|
@@ -51,6 +54,7 @@ pub enum Error {
|
|
|
Serde(btserde::Error),
|
|
|
Crypto(crypto::Error),
|
|
|
IncorrectSize { expected: usize, actual: usize },
|
|
|
+ NoBlockKey,
|
|
|
Custom(Box<dyn std::fmt::Debug + Send + Sync>),
|
|
|
}
|
|
|
|
|
@@ -70,6 +74,7 @@ impl Display for Error {
|
|
|
Error::IncorrectSize { expected, actual } => {
|
|
|
write!(f, "incorrect size {actual}, expected {expected}")
|
|
|
}
|
|
|
+ Error::NoBlockKey => write!(f, "no block key is present"),
|
|
|
Error::Custom(err) => err.fmt(f),
|
|
|
}
|
|
|
}
|
|
@@ -184,12 +189,18 @@ impl<T, U: Decompose<T>, S: TryCompose<T, U, Error = Infallible>> Compose<T, U>
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// Trait for accessing the metadata associated with a block.
|
|
|
pub trait MetaAccess {
|
|
|
- fn block_key(&self) -> Result<SymKey>;
|
|
|
- fn add_readcap_for(&mut self, owner: Principal, key: &dyn Encrypter) -> Result<()>;
|
|
|
- /// Returns the integrity value used to protect the contents of the block.
|
|
|
- fn integrity(&self) -> Option<&[u8]>;
|
|
|
- fn set_path(&mut self, path: BlockPath);
|
|
|
+ fn meta(&self) -> &BlockMeta;
|
|
|
+ fn mut_meta(&mut self) -> &mut BlockMeta;
|
|
|
+
|
|
|
+ fn meta_body(&self) -> &BlockMetaBody {
|
|
|
+ self.meta().body()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn mut_meta_body(&mut self) -> &mut BlockMetaBody {
|
|
|
+ self.mut_meta().mut_body()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// Extensions to the `Read` trait.
|
|
@@ -225,6 +236,108 @@ trait ReadExt: Read {
|
|
|
|
|
|
impl<T: Read> ReadExt for T {}
|
|
|
|
|
|
+trait HashExt: Hashable {
|
|
|
+ /// Returns the hash produced by the `DefaultHasher`.
|
|
|
+ fn default_hash(&self) -> u64 {
|
|
|
+ let mut hasher = DefaultHasher::new();
|
|
|
+ self.hash(&mut hasher);
|
|
|
+ hasher.finish()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Hashable> HashExt for T {}
|
|
|
+
|
|
|
+/// Metadata that is encrypted.
|
|
|
+#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Hash)]
|
|
|
+pub struct BlockMetaSecrets {
|
|
|
+ /// The inode number of the file.
|
|
|
+ inode: u64,
|
|
|
+ /// Mode of file.
|
|
|
+ mode: u32,
|
|
|
+ /// Owner UID of file.
|
|
|
+ uid: u32,
|
|
|
+ /// Owner GID of file.
|
|
|
+ gid: u32,
|
|
|
+ /// Last access time.
|
|
|
+ atime: Epoch,
|
|
|
+ /// Last data modification.
|
|
|
+ mtime: Epoch,
|
|
|
+ /// Last status change.
|
|
|
+ ctime: Epoch,
|
|
|
+ /// Size of the file in bytes.
|
|
|
+ size: u64,
|
|
|
+ /// Number of hard links to the file.
|
|
|
+ nlink: u32,
|
|
|
+ /// The sector size used by the block.
|
|
|
+ sect_sz: u32,
|
|
|
+ tags: BTreeMap<String, Vec<u8>>,
|
|
|
+}
|
|
|
+
|
|
|
+impl BlockMetaSecrets {
|
|
|
+ pub fn new() -> BlockMetaSecrets {
|
|
|
+ Self::default()
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn attr(&self) -> Attr {
|
|
|
+ self.into()
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn stat(&self) -> stat64 {
|
|
|
+ self.attr().into()
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Returns the number of sectors occupied by the block's data.
|
|
|
+ pub fn sectors(&self) -> u64 {
|
|
|
+ let sect_sz = self.sect_sz as u64;
|
|
|
+ if self.size % sect_sz == 0 {
|
|
|
+ self.size / sect_sz
|
|
|
+ } else {
|
|
|
+ self.size / sect_sz + 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Default for BlockMetaSecrets {
|
|
|
+ fn default() -> Self {
|
|
|
+ Self {
|
|
|
+ inode: 0,
|
|
|
+ mode: 0,
|
|
|
+ uid: 0,
|
|
|
+ gid: 0,
|
|
|
+ atime: Epoch::default(),
|
|
|
+ mtime: Epoch::default(),
|
|
|
+ ctime: Epoch::default(),
|
|
|
+ size: 0,
|
|
|
+ nlink: 0,
|
|
|
+ sect_sz: SECTOR_SZ_DEFAULT.try_into().unwrap(),
|
|
|
+ tags: BTreeMap::new(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl From<&BlockMetaSecrets> for Attr {
|
|
|
+ fn from(value: &BlockMetaSecrets) -> Self {
|
|
|
+ Attr {
|
|
|
+ ino: value.inode,
|
|
|
+ size: value.size,
|
|
|
+ atime: value.atime.value(),
|
|
|
+ mtime: value.mtime.value(),
|
|
|
+ ctime: value.ctime.value(),
|
|
|
+ atimensec: 0,
|
|
|
+ mtimensec: 0,
|
|
|
+ ctimensec: 0,
|
|
|
+ mode: value.mode,
|
|
|
+ nlink: value.nlink,
|
|
|
+ uid: value.uid,
|
|
|
+ gid: value.gid,
|
|
|
+ rdev: 0,
|
|
|
+ blksize: value.sect_sz,
|
|
|
+ blocks: value.sectors(),
|
|
|
+ flags: 0,
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/// This struct contains all of the metadata fields associated with a block, except for its
|
|
|
/// signature. Since this struct implements `Serialize`, this allows for convenient signature
|
|
|
/// calculations.
|
|
@@ -240,33 +353,142 @@ pub struct BlockMetaBody {
|
|
|
integrity: Option<VarHash>,
|
|
|
/// The public key that corresponds to the private key used to sign these metadata.
|
|
|
signing_key: AsymKeyPub<Sign>,
|
|
|
+ /// Additional metadata which is subject to confidentiality protection.
|
|
|
+ secrets: Ciphertext<BlockMetaSecrets>,
|
|
|
+
|
|
|
+ #[serde(skip)]
|
|
|
+ /// The cleartext block key.
|
|
|
+ block_key: Option<SymKey>,
|
|
|
+ #[serde(skip)]
|
|
|
+ /// The clear text secret metadata.
|
|
|
+ secrets_struct: Option<BlockMetaSecrets>,
|
|
|
}
|
|
|
|
|
|
impl BlockMetaBody {
|
|
|
- fn new<C: Creds>(creds: &C) -> BlockMetaBody {
|
|
|
- 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)?);
|
|
|
+ Ok(BlockMetaBody {
|
|
|
path: BlockPath::default(),
|
|
|
inherit: None,
|
|
|
- readcaps: BTreeMap::new(),
|
|
|
+ readcaps,
|
|
|
writecap: creds.writecap().map(|e| e.to_owned()),
|
|
|
integrity: None,
|
|
|
signing_key: creds.public_sign().to_owned(),
|
|
|
+ secrets: block_key.ser_encrypt(&BlockMetaSecrets::default())?,
|
|
|
+ block_key: Some(block_key),
|
|
|
+ secrets_struct: None,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn unlock_block_key<C: Creds>(&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(())
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn access_secrets<T, F: FnOnce(&mut BlockMetaSecrets) -> Result<T>>(
|
|
|
+ &mut self,
|
|
|
+ accessor: F,
|
|
|
+ ) -> Result<T> {
|
|
|
+ let secrets = match self.secrets_struct.as_mut() {
|
|
|
+ Some(secrets) => secrets,
|
|
|
+ None => {
|
|
|
+ let block_key = self.block_key()?;
|
|
|
+ self.secrets_struct = Some(block_key.ser_decrypt(&self.secrets)?);
|
|
|
+ self.secrets_struct.as_mut().unwrap()
|
|
|
+ }
|
|
|
+ };
|
|
|
+ let prev = secrets.default_hash();
|
|
|
+ let output = accessor(secrets)?;
|
|
|
+ if prev != secrets.default_hash() {
|
|
|
+ self.secrets = self
|
|
|
+ .block_key
|
|
|
+ .as_ref()
|
|
|
+ .ok_or(Error::NoBlockKey)?
|
|
|
+ .ser_encrypt(secrets)?;
|
|
|
}
|
|
|
+ Ok(output)
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn secrets(&self) -> Result<&BlockMetaSecrets> {
|
|
|
+ self.secrets_struct
|
|
|
+ .as_ref()
|
|
|
+ .ok_or_else(|| Error::custom("secrets have not been decrypted"))
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn block_key(&self) -> Result<&SymKey> {
|
|
|
+ self.block_key.as_ref().ok_or(Error::NoBlockKey)
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn use_block_key_for<C: Creds>(&mut self, creds: &C) -> Result<&SymKey> {
|
|
|
+ let readcap = self
|
|
|
+ .readcaps
|
|
|
+ .get(&creds.principal())
|
|
|
+ .ok_or(crypto::Error::NoReadCap)?;
|
|
|
+ let block_key = creds.ser_decrypt(readcap)?;
|
|
|
+ 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 integrity(&self) -> Option<&[u8]> {
|
|
|
+ self.integrity.as_ref().map(|hash| hash.as_slice())
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 set_path(&mut self, path: BlockPath) {
|
|
|
+ self.path = path
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// Signed metadata associated with a block.
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
-struct BlockMeta {
|
|
|
+pub struct BlockMeta {
|
|
|
body: BlockMetaBody,
|
|
|
sig: Signature,
|
|
|
}
|
|
|
|
|
|
impl BlockMeta {
|
|
|
- fn new<C: Creds>(creds: &C) -> BlockMeta {
|
|
|
- let body = BlockMetaBody::new(creds);
|
|
|
+ fn new<C: Creds>(creds: &C) -> Result<BlockMeta> {
|
|
|
+ let body = BlockMetaBody::new(creds)?;
|
|
|
let sig = Signature::empty(body.signing_key.scheme());
|
|
|
- BlockMeta { body, sig }
|
|
|
+ Ok(BlockMeta { body, sig })
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn body(&self) -> &BlockMetaBody {
|
|
|
+ self.as_ref()
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn mut_body(&mut self) -> &mut BlockMetaBody {
|
|
|
+ self.as_mut()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl AsRef<BlockMetaBody> for BlockMeta {
|
|
|
+ fn as_ref(&self) -> &BlockMetaBody {
|
|
|
+ &self.body
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl AsMut<BlockMetaBody> for BlockMeta {
|
|
|
+ fn as_mut(&mut self) -> &mut BlockMetaBody {
|
|
|
+ &mut self.body
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -286,14 +508,12 @@ impl<T: Read + Seek, C: Creds> BlockStream<T, C> {
|
|
|
// We need to use the writecap and signing_key provided by the current credentials.
|
|
|
meta.body.writecap = creds.writecap().map(|e| e.to_owned());
|
|
|
meta.body.signing_key = creds.public_sign().to_owned();
|
|
|
+ meta.body.use_block_key_for(&creds)?;
|
|
|
meta
|
|
|
}
|
|
|
None => {
|
|
|
- let mut meta = BlockMeta::new(&creds);
|
|
|
- let block_key = SymKey::generate(SymKeyKind::default())?;
|
|
|
- meta.body
|
|
|
- .readcaps
|
|
|
- .insert(creds.principal(), creds.ser_encrypt(&block_key)?);
|
|
|
+ let mut meta = BlockMeta::new(&creds)?;
|
|
|
+ meta.mut_body().add_readcap_for(creds.principal(), &creds)?;
|
|
|
meta.body.writecap = creds.writecap().map(|e| e.to_owned());
|
|
|
meta
|
|
|
}
|
|
@@ -349,29 +569,12 @@ impl<T: Seek, C> Seek for BlockStream<T, C> {
|
|
|
}
|
|
|
|
|
|
impl<T, C: Decrypter + Principaled> MetaAccess for BlockStream<T, C> {
|
|
|
- fn block_key(&self) -> Result<SymKey> {
|
|
|
- let readcap = self
|
|
|
- .meta
|
|
|
- .body
|
|
|
- .readcaps
|
|
|
- .get(&self.creds.principal())
|
|
|
- .ok_or(Error::Crypto(crypto::Error::NoReadCap))?;
|
|
|
- Ok(self.creds.ser_decrypt(readcap)?)
|
|
|
+ fn meta(&self) -> &BlockMeta {
|
|
|
+ &self.meta
|
|
|
}
|
|
|
|
|
|
- fn add_readcap_for(&mut self, owner: Principal, key: &dyn Encrypter) -> Result<()> {
|
|
|
- let block_key = self.block_key()?;
|
|
|
- let readcap = key.ser_encrypt(&block_key)?;
|
|
|
- self.meta.body.readcaps.insert(owner, readcap);
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- fn integrity(&self) -> Option<&[u8]> {
|
|
|
- self.meta.body.integrity.as_ref().map(|hash| hash.as_ref())
|
|
|
- }
|
|
|
-
|
|
|
- fn set_path(&mut self, path: BlockPath) {
|
|
|
- self.meta.body.path = path;
|
|
|
+ fn mut_meta(&mut self) -> &mut BlockMeta {
|
|
|
+ &mut self.meta
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -465,9 +668,14 @@ impl<T: Read + Write + Seek + FileReadWriteVolatile + 'static, C: Creds + 'stati
|
|
|
{
|
|
|
pub fn open(self) -> Result<Box<dyn Block>> {
|
|
|
let stream = BlockStream::new(self.inner, self.creds)?;
|
|
|
- let block_key = stream.block_key()?;
|
|
|
+ let block_key = stream.meta_body().block_key().map(|e| e.to_owned())?;
|
|
|
let mut stream = MerkleStream::new(stream)?;
|
|
|
stream.assert_root_integrity()?;
|
|
|
+ let sect_sz = stream.sector_sz();
|
|
|
+ stream.mut_meta_body().access_secrets(|secrets| {
|
|
|
+ secrets.sect_sz = sect_sz.try_into()?;
|
|
|
+ Ok(())
|
|
|
+ })?;
|
|
|
if self.encrypt {
|
|
|
let stream = SecretStream::new(block_key).try_compose(stream)?;
|
|
|
let stream = SectoredBuf::new().try_compose(stream)?;
|
|
@@ -721,21 +929,41 @@ pub trait Principaled {
|
|
|
}
|
|
|
|
|
|
/// An instant in time represented by the number of seconds since January 1st 1970, 00:00:00 UTC.
|
|
|
-#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
|
|
|
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
|
|
|
pub struct Epoch(u64);
|
|
|
|
|
|
impl Epoch {
|
|
|
/// Returns the current epoch time.
|
|
|
- fn now() -> Epoch {
|
|
|
+ pub fn now() -> Epoch {
|
|
|
let now = SystemTime::now();
|
|
|
// If the system clock is before the unix epoch, just panic.
|
|
|
let epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
|
|
|
Epoch(epoch.as_secs())
|
|
|
}
|
|
|
+
|
|
|
+ pub fn from_value(value: u64) -> Self {
|
|
|
+ value.into()
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn value(self) -> u64 {
|
|
|
+ self.into()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl Copy for Epoch {}
|
|
|
|
|
|
+impl From<u64> for Epoch {
|
|
|
+ fn from(value: u64) -> Self {
|
|
|
+ Self(value)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl From<Epoch> for u64 {
|
|
|
+ fn from(value: Epoch) -> Self {
|
|
|
+ value.0
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
impl Add<Duration> for Epoch {
|
|
|
type Output = Self;
|
|
|
fn add(self, other: Duration) -> Self {
|
|
@@ -786,6 +1014,7 @@ mod tests {
|
|
|
};
|
|
|
|
|
|
use super::*;
|
|
|
+ use btserde::{from_vec, to_vec};
|
|
|
use tempdir::TempDir;
|
|
|
use test_helpers::*;
|
|
|
|
|
@@ -826,6 +1055,32 @@ mod tests {
|
|
|
.expect("failed to open block");
|
|
|
}
|
|
|
|
|
|
+ /// Tests that the `BlockMetaBody` struct has an updated secrets struct after it is modified
|
|
|
+ /// in the `access_secrets` method.
|
|
|
+ #[test]
|
|
|
+ fn block_meta_body_secrets_updated_after_access() {
|
|
|
+ const UID: u32 = 1000;
|
|
|
+ let creds = test_helpers::NODE_CREDS.clone();
|
|
|
+
|
|
|
+ let vec = {
|
|
|
+ let mut body = BlockMetaBody::new(&creds).expect("failed to create meta body");
|
|
|
+ body.access_secrets(|secrets| {
|
|
|
+ secrets.uid = UID;
|
|
|
+ Ok(())
|
|
|
+ })
|
|
|
+ .expect("access secrets failed");
|
|
|
+ to_vec(&body).expect("to_vec failed")
|
|
|
+ };
|
|
|
+
|
|
|
+ let mut body = from_vec::<BlockMetaBody>(&vec).expect("from_vec failed");
|
|
|
+ body.unlock_block_key(&creds)
|
|
|
+ .expect("unlock_block_key failed");
|
|
|
+ let actual_uid = body
|
|
|
+ .access_secrets(|secrets| Ok(secrets.uid))
|
|
|
+ .expect("access_secrets failed");
|
|
|
+ assert_eq!(UID, actual_uid);
|
|
|
+ }
|
|
|
+
|
|
|
struct BlockTestCase {
|
|
|
root_creds: TpmCreds,
|
|
|
node_creds: TpmCreds,
|
|
@@ -881,7 +1136,9 @@ mod tests {
|
|
|
.with_encrypt(true)
|
|
|
.open()
|
|
|
.expect("failed to open block");
|
|
|
- block.set_path(self.node_creds.writecap().unwrap().body.path.clone());
|
|
|
+ block
|
|
|
+ .mut_meta_body()
|
|
|
+ .set_path(self.node_creds.writecap().unwrap().body.path.clone());
|
|
|
block
|
|
|
}
|
|
|
|
|
@@ -968,6 +1225,7 @@ mod tests {
|
|
|
{
|
|
|
let mut block = case.open_new(&path);
|
|
|
block
|
|
|
+ .mut_meta_body()
|
|
|
.add_readcap_for(app_creds.principal(), &app_creds)
|
|
|
.expect("failed to add readcap");
|
|
|
block.write(&EXPECTED[..MID]).expect("first write failed");
|