|
@@ -44,6 +44,13 @@ use std::{
|
|
|
};
|
|
|
use strum_macros::{Display, EnumDiscriminants, FromRepr};
|
|
|
|
|
|
+#[macro_export]
|
|
|
+macro_rules! bterr {
|
|
|
+ ($err:expr) => {
|
|
|
+ $err
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
#[derive(Debug)]
|
|
|
pub enum Error {
|
|
|
MissingWritecap,
|
|
@@ -166,7 +173,7 @@ impl<T, E: Display> StrInIoErr<T> for std::result::Result<T, E> {
|
|
|
}
|
|
|
|
|
|
/// The default sector size to use for new blocks.
|
|
|
-const SECTOR_SZ_DEFAULT: usize = 4096;
|
|
|
+pub const SECTOR_SZ_DEFAULT: usize = 4096;
|
|
|
|
|
|
/// ### THE BLOCK TRAIT
|
|
|
///
|
|
@@ -207,14 +214,31 @@ pub trait Sectored {
|
|
|
// Returns the size of the sector for this stream.
|
|
|
fn sector_sz(&self) -> usize;
|
|
|
|
|
|
+ /// Returns `Err(Error::IncorrectSize)` if the given size is not equal to the sector size.
|
|
|
fn assert_sector_sz(&self, actual: usize) -> Result<()> {
|
|
|
let expected = self.sector_sz();
|
|
|
- if expected == actual {
|
|
|
- Ok(())
|
|
|
+ if expected != actual {
|
|
|
+ Err(Error::IncorrectSize { expected, actual })
|
|
|
} else {
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Returns `Err(Error::IncorrectSize)` if the given size is less than the sector size.
|
|
|
+ fn assert_at_least_sector_sz(&self, actual: usize) -> Result<()> {
|
|
|
+ let expected = self.sector_sz();
|
|
|
+ if actual < expected {
|
|
|
Err(Error::IncorrectSize { expected, actual })
|
|
|
+ } else {
|
|
|
+ Ok(())
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /// Returns the offset (in bytes) from the beginning of this stream that the given 0-based
|
|
|
+ /// sector index corresponds to.
|
|
|
+ fn offset_at(&self, index: usize) -> usize {
|
|
|
+ index * self.sector_sz()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// A version of the `Write` trait, which allows integrity information to be supplied when flushing.
|
|
@@ -325,11 +349,30 @@ trait HashExt: Hashable {
|
|
|
|
|
|
impl<T: Hashable> HashExt for T {}
|
|
|
|
|
|
+/// A unique identifier for a block.
|
|
|
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Hash)]
|
|
|
+pub struct BlockId {
|
|
|
+ generation: u64,
|
|
|
+ inode: u64,
|
|
|
+}
|
|
|
+
|
|
|
+impl BlockId {
|
|
|
+ pub fn new(generation: u64, inode: u64) -> BlockId {
|
|
|
+ BlockId { generation, inode }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Default for BlockId {
|
|
|
+ fn default() -> Self {
|
|
|
+ BlockId::new(0, 0)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/// Metadata that is encrypted.
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Hash)]
|
|
|
pub struct BlockMetaSecrets {
|
|
|
- /// The inode number of the file.
|
|
|
- inode: u64,
|
|
|
+ /// The identifier for the block these secrets are for.
|
|
|
+ block_id: BlockId,
|
|
|
/// Mode of file.
|
|
|
mode: u32,
|
|
|
/// Owner UID of file.
|
|
@@ -348,16 +391,46 @@ pub struct BlockMetaSecrets {
|
|
|
nlink: u32,
|
|
|
/// The sector size used by the block.
|
|
|
sect_sz: u32,
|
|
|
+ /// User controlled metadata.
|
|
|
tags: BTreeMap<String, Vec<u8>>,
|
|
|
}
|
|
|
|
|
|
impl BlockMetaSecrets {
|
|
|
pub fn new() -> BlockMetaSecrets {
|
|
|
- Self::default()
|
|
|
+ Self {
|
|
|
+ block_id: BlockId::default(),
|
|
|
+ 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(),
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
pub fn attr(&self) -> Attr {
|
|
|
- self.into()
|
|
|
+ Attr {
|
|
|
+ ino: self.block_id.inode,
|
|
|
+ size: self.size,
|
|
|
+ atime: self.atime.value(),
|
|
|
+ mtime: self.mtime.value(),
|
|
|
+ ctime: self.ctime.value(),
|
|
|
+ atimensec: 0,
|
|
|
+ mtimensec: 0,
|
|
|
+ ctimensec: 0,
|
|
|
+ mode: self.mode,
|
|
|
+ nlink: self.nlink,
|
|
|
+ uid: self.uid,
|
|
|
+ gid: self.gid,
|
|
|
+ rdev: 0,
|
|
|
+ blksize: self.sect_sz,
|
|
|
+ blocks: self.sectors(),
|
|
|
+ flags: 0,
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
pub fn stat(&self) -> stat64 {
|
|
@@ -373,46 +446,31 @@ impl BlockMetaSecrets {
|
|
|
self.size / sect_sz + 1
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ pub fn block_id(&self) -> &BlockId {
|
|
|
+ &self.block_id
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn sector_sz(&self) -> u32 {
|
|
|
+ self.sect_sz
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
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(),
|
|
|
- }
|
|
|
+ Self::new()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl AsRef<BlockId> for BlockMetaSecrets {
|
|
|
+ fn as_ref(&self) -> &BlockId {
|
|
|
+ self.block_id()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
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,
|
|
|
- }
|
|
|
+ value.attr()
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -464,7 +522,7 @@ impl BlockMetaBody {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- pub fn unlock_block_key<C: Creds>(&mut self, creds: &C) -> Result<()> {
|
|
|
+ pub fn unlock_block_key<C: Decrypter + Principaled>(&mut self, creds: &C) -> Result<()> {
|
|
|
if self.block_key.is_some() {
|
|
|
return Ok(());
|
|
|
}
|
|
@@ -480,26 +538,25 @@ impl BlockMetaBody {
|
|
|
&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();
|
|
|
+ self.decrypt_secrets()?;
|
|
|
+ let secrets = self.secrets_struct.as_mut().unwrap();
|
|
|
let output = accessor(secrets)?;
|
|
|
- if prev != secrets.default_hash() {
|
|
|
- self.secrets = self
|
|
|
- .block_key
|
|
|
- .as_ref()
|
|
|
- .ok_or(Error::NoBlockKey)?
|
|
|
- .ser_encrypt(secrets)?;
|
|
|
- }
|
|
|
+ self.secrets = self
|
|
|
+ .block_key
|
|
|
+ .as_ref()
|
|
|
+ .ok_or(Error::NoBlockKey)?
|
|
|
+ .ser_encrypt(secrets)?;
|
|
|
Ok(output)
|
|
|
}
|
|
|
|
|
|
+ pub fn decrypt_secrets(&mut self) -> Result<()> {
|
|
|
+ if self.secrets_struct.is_none() {
|
|
|
+ let block_key = self.block_key()?;
|
|
|
+ self.secrets_struct = Some(block_key.ser_decrypt(&self.secrets)?);
|
|
|
+ }
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
pub fn secrets(&self) -> Result<&BlockMetaSecrets> {
|
|
|
self.secrets_struct
|
|
|
.as_ref()
|
|
@@ -510,7 +567,16 @@ impl BlockMetaBody {
|
|
|
self.block_key.as_ref().ok_or(Error::NoBlockKey)
|
|
|
}
|
|
|
|
|
|
- pub fn use_block_key_for<C: Creds>(&mut self, creds: &C) -> Result<&SymKey> {
|
|
|
+ pub fn block_id(&self) -> Result<&BlockId> {
|
|
|
+ let secrets = self.secrets()?;
|
|
|
+ Ok(secrets.block_id())
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn integrity(&self) -> Option<&[u8]> {
|
|
|
+ self.integrity.as_ref().map(|hash| hash.as_slice())
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn use_readcap_for<C: Creds>(&mut self, creds: &C) -> Result<&SymKey> {
|
|
|
let readcap = self
|
|
|
.readcaps
|
|
|
.get(&creds.principal())
|
|
@@ -524,10 +590,6 @@ impl BlockMetaBody {
|
|
|
&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)?);
|
|
@@ -579,6 +641,7 @@ struct BlockStream<T, C> {
|
|
|
meta: BlockMeta,
|
|
|
meta_body_buf: Vec<u8>,
|
|
|
creds: C,
|
|
|
+ sect_sz: usize,
|
|
|
}
|
|
|
|
|
|
impl<T: Read + Seek, C: Creds> BlockStream<T, C> {
|
|
@@ -588,11 +651,12 @@ impl<T: Read + Seek, C: Creds> BlockStream<T, C> {
|
|
|
Some(mut meta) => {
|
|
|
meta.assert_valid(&block_path)?;
|
|
|
meta.body.path = block_path;
|
|
|
+ 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.writecap().ok_or(Error::MissingWritecap)?.to_owned());
|
|
|
meta.body.signing_key = creds.public_sign().to_owned();
|
|
|
- meta.body.use_block_key_for(&creds)?;
|
|
|
+ meta.body.decrypt_secrets()?;
|
|
|
meta
|
|
|
}
|
|
|
None => {
|
|
@@ -604,11 +668,13 @@ impl<T: Read + Seek, C: Creds> BlockStream<T, C> {
|
|
|
meta
|
|
|
}
|
|
|
};
|
|
|
+ let sect_sz = meta.body.secrets()?.sector_sz().try_into().box_err()?;
|
|
|
Ok(BlockStream {
|
|
|
trailered,
|
|
|
- meta,
|
|
|
meta_body_buf: Vec::new(),
|
|
|
creds,
|
|
|
+ sect_sz,
|
|
|
+ meta,
|
|
|
})
|
|
|
}
|
|
|
}
|
|
@@ -629,10 +695,7 @@ impl<T: Write + Seek, C: Signer + Principaled + Decrypter> Write for BlockStream
|
|
|
}
|
|
|
|
|
|
fn flush(&mut self) -> io::Result<()> {
|
|
|
- Err(io::Error::new(
|
|
|
- io::ErrorKind::Unsupported,
|
|
|
- "flush is not supported, use flush_integ instead",
|
|
|
- ))
|
|
|
+ self.sign_flush_meta()
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -673,12 +736,24 @@ impl<T, C> AsMut<BlockMeta> for BlockStream<T, C> {
|
|
|
|
|
|
impl<T, C> MetaAccess for BlockStream<T, C> {}
|
|
|
|
|
|
+impl<T, C> Sectored for BlockStream<T, C> {
|
|
|
+ fn sector_sz(&self) -> usize {
|
|
|
+ self.sect_sz
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
impl<T: Read + Write + Seek, C: Creds> Block for BlockStream<T, C> {
|
|
|
fn flush_meta(&mut self) -> Result<()> {
|
|
|
self.sign_flush_meta().map_err(|err| err.into())
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+impl<T, C> Decompose<(T, C)> for BlockStream<T, C> {
|
|
|
+ fn into_inner(self) -> (T, C) {
|
|
|
+ (self.trailered.into_inner(), self.creds)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
pub struct BlockOpenOptions<T, C> {
|
|
|
inner: T,
|
|
|
creds: C,
|
|
@@ -743,11 +818,6 @@ impl<T: Read + Write + Seek + 'static, C: Creds + 'static> BlockOpenOptions<T, C
|
|
|
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)?;
|
|
@@ -1143,14 +1213,10 @@ impl<F: FnOnce()> Drop for DropTrigger<F> {
|
|
|
mod tests {
|
|
|
use std::{fs::OpenOptions, io::Cursor, path::PathBuf};
|
|
|
|
|
|
- use crate::crypto::{
|
|
|
- tpm::{TpmCredStore, TpmCreds},
|
|
|
- ConcreteCreds, CredStore, CredsPriv,
|
|
|
- };
|
|
|
+ use crate::crypto::{ConcreteCreds, CredsPriv, SignStream};
|
|
|
|
|
|
use super::*;
|
|
|
use btserde::{from_vec, to_vec};
|
|
|
- use swtpm_harness::SwtpmHarness;
|
|
|
use tempdir::TempDir;
|
|
|
use test_helpers::*;
|
|
|
|
|
@@ -1202,28 +1268,111 @@ mod tests {
|
|
|
assert_eq!(UID, actual_uid);
|
|
|
}
|
|
|
|
|
|
- struct BlockTestCase {
|
|
|
- root_creds: TpmCreds,
|
|
|
- node_creds: TpmCreds,
|
|
|
- _swtpm: SwtpmHarness,
|
|
|
+ struct InMemTestCase {
|
|
|
+ node_creds: ConcreteCreds,
|
|
|
+ block_path: BlockPath,
|
|
|
+ block_id: BlockId,
|
|
|
+ }
|
|
|
+
|
|
|
+ type EncBlock = SectoredBuf<
|
|
|
+ SecretStream<SignStream<BlockStream<Cursor<Vec<u8>>, ConcreteCreds>, ConcreteCreds>>,
|
|
|
+ >;
|
|
|
+
|
|
|
+ impl InMemTestCase {
|
|
|
+ fn new() -> InMemTestCase {
|
|
|
+ let components = vec!["nodes".to_string(), "phone".to_string()];
|
|
|
+ let node_creds = {
|
|
|
+ let mut node_creds = node_creds().clone();
|
|
|
+ let writecap = root_creds()
|
|
|
+ .issue_writecap(
|
|
|
+ node_creds.principal(),
|
|
|
+ components.clone(),
|
|
|
+ Epoch::now() + Duration::from_secs(3600),
|
|
|
+ )
|
|
|
+ .expect("failed to issue writecap");
|
|
|
+ node_creds.set_writecap(writecap);
|
|
|
+ node_creds
|
|
|
+ };
|
|
|
+ let block_path = BlockPath::new(root_creds().principal(), components);
|
|
|
+ let block_id = BlockId::default();
|
|
|
+ Self {
|
|
|
+ node_creds,
|
|
|
+ block_path,
|
|
|
+ block_id,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn stream(&self, vec: Vec<u8>) -> EncBlock {
|
|
|
+ let inner = Cursor::new(vec);
|
|
|
+ let mut stream =
|
|
|
+ BlockStream::new(inner, self.node_creds.clone(), self.block_path.clone()).unwrap();
|
|
|
+ stream
|
|
|
+ .mut_meta_body()
|
|
|
+ .access_secrets(|secrets| {
|
|
|
+ secrets.block_id = self.block_id.clone();
|
|
|
+ Ok(())
|
|
|
+ })
|
|
|
+ .unwrap();
|
|
|
+ let block_key = stream.meta_body().block_key().unwrap().to_owned();
|
|
|
+ let stream = SignStream::new(stream, self.node_creds.clone()).unwrap();
|
|
|
+ let stream = SecretStream::new(block_key).try_compose(stream).unwrap();
|
|
|
+ SectoredBuf::new().try_compose(stream).unwrap()
|
|
|
+ }
|
|
|
+
|
|
|
+ fn into_vec(stream: EncBlock) -> Vec<u8> {
|
|
|
+ stream
|
|
|
+ .into_inner()
|
|
|
+ .into_inner()
|
|
|
+ .into_inner()
|
|
|
+ .into_inner()
|
|
|
+ .0
|
|
|
+ .into_inner()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn block_write_read_with_cursor() {
|
|
|
+ const EXPECTED: &[u8] = b"Silly sordid sulking sultans.";
|
|
|
+ let case = InMemTestCase::new();
|
|
|
+ let mut stream = case.stream(Vec::new());
|
|
|
+
|
|
|
+ stream.write_all(EXPECTED).unwrap();
|
|
|
+ stream.flush().unwrap();
|
|
|
+
|
|
|
+ let vec = InMemTestCase::into_vec(stream);
|
|
|
+ let mut stream = case.stream(vec);
|
|
|
+ let mut actual = [0u8; EXPECTED.len()];
|
|
|
+ stream.read(&mut actual).unwrap();
|
|
|
+
|
|
|
+ assert_eq!(EXPECTED, actual);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn block_write_multiple() {
|
|
|
+ const ITER: usize = 16;
|
|
|
+ let case = InMemTestCase::new();
|
|
|
+ let mut stream = case.stream(Vec::new());
|
|
|
+ let expected = vec![1u8; stream.sector_sz()];
|
|
|
+
|
|
|
+ for _ in 0..ITER {
|
|
|
+ stream.write(&expected).unwrap();
|
|
|
+ }
|
|
|
+ stream.flush().unwrap();
|
|
|
+ }
|
|
|
+
|
|
|
+ pub struct BlockTestCase {
|
|
|
temp_dir: TempDir,
|
|
|
root_path: BlockPath,
|
|
|
node_path: BlockPath,
|
|
|
+ root_creds: ConcreteCreds,
|
|
|
+ node_creds: ConcreteCreds,
|
|
|
}
|
|
|
|
|
|
impl BlockTestCase {
|
|
|
- const ROOT_PASSWORD: &'static str = "(1337Prestidigitation7331)";
|
|
|
-
|
|
|
fn new() -> BlockTestCase {
|
|
|
let temp_dir = TempDir::new("block_test").expect("failed to create temp dir");
|
|
|
- let swtpm = SwtpmHarness::new().expect("failed to start swtpm");
|
|
|
- let context = swtpm.context().expect("failed to retrieve context");
|
|
|
- let cred_store = TpmCredStore::new(context, swtpm.state_path().to_owned())
|
|
|
- .expect("failed to create TpmCredStore");
|
|
|
- let root_creds = cred_store
|
|
|
- .gen_root_creds(Self::ROOT_PASSWORD)
|
|
|
- .expect("failed to get root creds");
|
|
|
- let mut node_creds = cred_store.node_creds().expect("failed to get node creds");
|
|
|
+ let root_creds = test_helpers::ROOT_CREDS.clone();
|
|
|
+ let mut node_creds = test_helpers::NODE_CREDS.clone();
|
|
|
let components = vec!["nodes".to_string(), "phone".to_string()];
|
|
|
let writecap = root_creds
|
|
|
.issue_writecap(
|
|
@@ -1232,15 +1381,12 @@ mod tests {
|
|
|
Epoch::now() + Duration::from_secs(3600),
|
|
|
)
|
|
|
.expect("failed to issue writecap");
|
|
|
- cred_store
|
|
|
- .assign_node_writecap(&mut node_creds, writecap)
|
|
|
- .expect("failed to assign node writecap");
|
|
|
+ node_creds.set_writecap(writecap);
|
|
|
let case = BlockTestCase {
|
|
|
temp_dir,
|
|
|
- _swtpm: swtpm,
|
|
|
- node_creds,
|
|
|
node_path: BlockPath::new(root_creds.principal(), components),
|
|
|
root_path: BlockPath::new(root_creds.principal(), vec![]),
|
|
|
+ node_creds,
|
|
|
root_creds,
|
|
|
};
|
|
|
std::fs::create_dir_all(case.fs_path(&case.node_path))
|