#![allow(incomplete_features)] #![feature(trait_upcasting)] mod block_path; pub use block_path::{BlockPath, BlockPathError}; pub mod blocktree; /// Code which enables cryptographic operations. pub mod crypto; pub mod msg; mod sectored_buf; mod trailered; #[cfg(feature = "testing")] pub mod test_helpers; #[macro_use] extern crate static_assertions; #[cfg(feature = "testing")] #[macro_use] extern crate lazy_static; use brotli::{CompressorWriter, Decompressor}; use crypto::{ AsymKeyPub, Ciphertext, Creds, Decrypter, DecrypterExt, Encrypter, EncrypterExt, HashKind, MerkleStream, PublicCreds, SecretStream, Sign, Signature, Signer, SymKey, SymKeyKind, VarHash, }; 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, hash_map::DefaultHasher, BTreeMap, HashMap}, convert::{Infallible, TryFrom}, fmt::{self, Display, Formatter}, hash::{Hash as Hashable, Hasher}, io::{self, Read, Seek, SeekFrom, Write}, net::SocketAddr, ops::{Add, Sub}, sync::PoisonError, time::{Duration, SystemTime}, }; use trailered::Trailered; #[derive(Debug)] pub enum Error { MissingWritecap, Io(std::io::Error), Serde(btserde::Error), Crypto(crypto::Error), IncorrectSize { expected: usize, actual: usize }, NoBlockKey, Custom(Box), } impl Error { fn custom(err: E) -> Error { Error::Custom(Box::new(err)) } } impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Error::MissingWritecap => write!(f, "missing writecap"), Error::Io(err) => err.fmt(f), Error::Serde(err) => err.fmt(f), Error::Crypto(err) => err.fmt(f), 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), } } } impl std::error::Error for Error {} impl From for Error { fn from(err: std::io::Error) -> Self { Error::Io(err) } } impl From for std::io::Error { fn from(err: Error) -> Self { io::Error::new(io::ErrorKind::Other, err) } } impl From for Error { fn from(err: btserde::Error) -> Self { Error::Serde(err) } } impl From for Error { fn from(err: crypto::Error) -> Self { Error::Crypto(err) } } impl From for Error { fn from(err: std::num::TryFromIntError) -> Self { Error::custom(err) } } impl From> for Error { fn from(err: PoisonError) -> Self { Error::custom(err.to_string()) } } type Result = std::result::Result; /// TODO: Remove this once the error_chain crate is integrated. trait BoxInIoErr { fn box_err(self) -> std::result::Result; } impl BoxInIoErr for std::result::Result { fn box_err(self) -> std::result::Result { self.map_err(|err| io::Error::new(io::ErrorKind::Other, err)) } } /// TODO: Remove this once the error_chain crate is integrated. trait StrInIoErr { fn str_err(self) -> std::result::Result; } impl StrInIoErr for std::result::Result { fn str_err(self) -> std::result::Result { self.map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string())) } } /// The default sector size to use for new blocks. const SECTOR_SZ_DEFAULT: usize = 4096; /// Trait for types which provide read and write access to blocks. pub trait Block: Read + Write + Seek + MetaAccess + FileReadWriteVolatile {} // A trait for streams which only allow reads and writes in fixed sized units called sectors. pub trait Sectored { // Returns the size of the sector for this stream. fn sector_sz(&self) -> usize; fn assert_sector_sz(&self, actual: usize) -> Result<()> { let expected = self.sector_sz(); if expected == actual { Ok(()) } else { Err(Error::IncorrectSize { expected, actual }) } } } /// A version of the `Write` trait, which allows integrity information to be supplied when flushing. pub trait WriteInteg: Write { fn flush_integ(&mut self, integrity: &[u8]) -> io::Result<()>; } trait Decompose { fn into_inner(self) -> T; } trait TryCompose> { type Error; fn try_compose(self, inner: T) -> std::result::Result; } trait Compose { fn compose(self, inner: T) -> U; } impl, S: TryCompose> Compose for S { fn compose(self, inner: T) -> U { let result = self.try_compose(inner); // Safety: Infallible has no values, so `result` must be `Ok`. unsafe { result.unwrap_unchecked() } } } /// Trait for accessing the metadata associated with a block. pub trait MetaAccess { 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. trait ReadExt: Read { /// Reads repeatedly until one of the following occur: /// 1. The given buffer is full. /// 2. A call to `read` returns 0. /// 3. A call to `read` returns an error. /// The number of bytes read is returned. If an error is returned, then no bytes were read. fn fill_buf(&mut self, mut dest: &mut [u8]) -> io::Result { let dest_len_start = dest.len(); while !dest.is_empty() { let byte_ct = match self.read(dest) { Ok(byte_ct) => byte_ct, Err(err) => { if dest_len_start == dest.len() { return Err(err); } else { // We're not allowed to return an error since we've already read from self. error!("an error occurred in fill_buf: {}", err); break; } } }; if 0 == byte_ct { break; } dest = &mut dest[byte_ct..]; } Ok(dest_len_start - dest.len()) } } impl 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 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>, } 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. #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct BlockMetaBody { path: BlockPath, /// 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>, readcaps: BTreeMap>, writecap: Option, /// A hash which provides integrity for the contents of the block body. integrity: Option, /// The public key that corresponds to the private key used to sign these metadata. signing_key: AsymKeyPub, /// Additional metadata which is subject to confidentiality protection. secrets: Ciphertext, #[serde(skip)] /// The cleartext block key. block_key: Option, #[serde(skip)] /// The clear text secret metadata. secrets_struct: Option, } impl BlockMetaBody { fn new(creds: &C) -> Result { 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, 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(&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 Result>( &mut self, accessor: F, ) -> Result { 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(&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 { &mut self.block_key } pub fn integrity(&self) -> Option<&[u8]> { self.integrity.as_ref().map(|hash| hash.as_slice()) } pub fn add_readcap_for(&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)] pub struct BlockMeta { body: BlockMetaBody, sig: Signature, } impl BlockMeta { fn new(creds: &C) -> Result { let body = BlockMetaBody::new(creds)?; let sig = Signature::empty(body.signing_key.scheme()); 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 for BlockMeta { fn as_ref(&self) -> &BlockMetaBody { &self.body } } impl AsMut for BlockMeta { fn as_mut(&mut self) -> &mut BlockMetaBody { &mut self.body } } struct BlockStream { trailered: Trailered, meta: BlockMeta, meta_body_buf: Vec, creds: C, } impl BlockStream { fn new(inner: T, creds: C) -> Result> { let (trailered, meta) = Trailered::<_, BlockMeta>::new(inner)?; let meta = match meta { Some(mut meta) => { meta.assert_valid()?; // 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)?; meta.mut_body().add_readcap_for(creds.principal(), &creds)?; meta.body.writecap = creds.writecap().map(|e| e.to_owned()); meta } }; Ok(BlockStream { trailered, meta, meta_body_buf: Vec::new(), creds, }) } } impl Write for BlockStream { fn write(&mut self, buf: &[u8]) -> io::Result { self.trailered.write(buf) } fn flush(&mut self) -> io::Result<()> { Err(io::Error::new( io::ErrorKind::Unsupported, "flush is not supported, use flush_integ instead", )) } } impl WriteInteg for BlockStream { fn flush_integ(&mut self, integrity: &[u8]) -> io::Result<()> { let meta_body = &mut self.meta.body; let integ = meta_body.integrity.get_or_insert_with(VarHash::default); integ.as_mut().copy_from_slice(integrity); self.meta_body_buf.clear(); self.meta.sig = self .creds .ser_sign_into(&meta_body, &mut self.meta_body_buf)?; self.trailered.flush(&self.meta)?; Ok(()) } } impl Read for BlockStream { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.trailered.read(buf) } } impl Seek for BlockStream { /// Seeks to the given position in the stream. If a position beyond the end of the stream is /// specified, the the seek will be to the end of the stream. fn seek(&mut self, pos: SeekFrom) -> io::Result { self.trailered.seek(pos) } } impl MetaAccess for BlockStream { fn meta(&self) -> &BlockMeta { &self.meta } fn mut_meta(&mut self) -> &mut BlockMeta { &mut self.meta } } impl FileReadWriteVolatile for BlockStream { fn read_volatile( &mut self, slice: fuse_backend_rs::file_buf::FileVolatileSlice, ) -> io::Result { self.trailered.read_volatile(slice) } fn write_volatile( &mut self, slice: fuse_backend_rs::file_buf::FileVolatileSlice, ) -> io::Result { self.trailered.write_volatile(slice) } fn read_at_volatile( &mut self, slice: fuse_backend_rs::file_buf::FileVolatileSlice, offset: u64, ) -> io::Result { self.trailered.read_at_volatile(slice, offset) } fn write_at_volatile( &mut self, slice: fuse_backend_rs::file_buf::FileVolatileSlice, offset: u64, ) -> io::Result { self.trailered.write_at_volatile(slice, offset) } } impl Block for BlockStream { } pub struct BlockOpenOptions { inner: T, creds: C, encrypt: bool, compress: bool, } impl BlockOpenOptions<(), ()> { pub fn new() -> BlockOpenOptions<(), ()> { BlockOpenOptions { inner: (), creds: (), encrypt: true, compress: true, } } } impl BlockOpenOptions { pub fn with_inner(self, inner: U) -> BlockOpenOptions { BlockOpenOptions { inner, creds: self.creds, encrypt: self.encrypt, compress: self.compress, } } pub fn with_creds(self, creds: D) -> BlockOpenOptions { BlockOpenOptions { inner: self.inner, creds, encrypt: self.encrypt, compress: self.compress, } } pub fn with_encrypt(mut self, encrypt: bool) -> Self { self.encrypt = encrypt; self } pub fn with_compress(mut self, compress: bool) -> Self { self.compress = compress; self } } impl BlockOpenOptions { pub fn open(self) -> Result> { let stream = BlockStream::new(self.inner, self.creds)?; 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)?; Ok(Box::new(stream)) } else { let stream = SectoredBuf::new().try_compose(stream)?; Ok(Box::new(stream)) } } } impl Default for BlockOpenOptions<(), ()> { fn default() -> Self { Self::new() } } impl Decompose for CompressorWriter { fn into_inner(self) -> T { self.into_inner() } } impl Decompose for Decompressor { fn into_inner(self) -> T { self.into_inner() } } #[derive(Clone)] pub struct BrotliParams { buf_sz: usize, quality: u32, window_sz: u32, } impl BrotliParams { pub fn new(buf_sz: usize, quality: u32, window_sz: u32) -> BrotliParams { BrotliParams { buf_sz, quality, window_sz, } } } impl TryCompose> for BrotliParams { type Error = Error; fn try_compose(self, inner: T) -> Result> { Ok(CompressorWriter::new( inner, self.buf_sz, self.quality, self.window_sz, )) } } impl TryCompose> for BrotliParams { type Error = Error; fn try_compose(self, inner: T) -> Result> { Ok(Decompressor::new(inner, self.buf_sz)) } } /// An envelopment of a key, which is tagged with the principal who the key is meant for. #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] pub struct Readcap { /// The principal this `Readcap` was issued to. issued_to: Principal, /// An encipherment of a block key using the public key of the principal. key: Ciphertext, } impl Readcap { pub fn new(issued_to: VarHash, key: Ciphertext) -> Readcap { Readcap { issued_to: Principal(issued_to), key, } } } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] pub struct WritecapBody { /// The principal this `Writecap` was issued to. issued_to: Principal, /// The path where this write caps's validity begins. path: BlockPath, /// The point in time after which this write cap is no longer valid. expires: Epoch, /// The public key used to sign this write cap. signing_key: AsymKeyPub, } /// Verifies that a principal is authorized to write blocks in a tree. #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] pub struct Writecap { /// A container for the fields of this writecap which are covered by the signature. body: WritecapBody, /// A digital signature which covers all of the fields in the write cap except for next. signature: Signature, /// The next write cap in the chain leading back to the root. next: Option>, } /// Fragments are created from blocks using Erasure Encoding and stored with other nodes in the /// network to provide availability and redundancy of data. #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Fragment { /// The path to the block this fragment is from. path: BlockPath, /// The serial number of this fragment. serial: FragmentSerial, /// The actual data. body: Vec, } impl Fragment { /// Create a new fragment with the given fields. If `path_str` cannot be parsed then a failed /// `Result` is returned containing a `PathError`. pub fn new( path_str: &str, serial_num: u32, body: Vec, ) -> std::result::Result { let result = BlockPath::try_from(path_str); Ok(Fragment { path: result?, serial: FragmentSerial(serial_num), body, }) } } #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] /// Structure for keeping track of block information in a directory. pub struct BlockRecord { /// The ID of the block. Note that this is only guaranteed to be unique in the directory this /// record is stored in. pub inode: u64, /// A mapping from fragment serial numbers to fragment records. /// TODO: Does this need to have a consistent order? pub frags: HashMap, } #[derive(Debug, PartialEq, Serialize, Deserialize)] /// Structure for keeping track of server information in a directory. pub struct ServerRecord { /// The most up-to-date address for this server. addr: SocketAddr, /// The public credentials for this server. pub_creds: PublicCreds, } #[derive(Debug, PartialEq, Serialize, Deserialize)] pub enum DirEntry { Directory(BlockRecord), File(BlockRecord), Server(ServerRecord), } /// This is the body contained in directory blocks. #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Directory { /// This block's descendants. entries: BTreeMap, } impl Directory { pub fn new() -> Directory { Directory { entries: BTreeMap::new(), } } pub fn add_file(&mut self, name: String, inode: u64) -> Result<&mut BlockRecord> { let entry = match self.entries.entry(name) { btree_map::Entry::Occupied(entry) => { return Err(Error::custom(format!( "directory already contains entry '{}'", entry.key() ))); } btree_map::Entry::Vacant(entry) => entry, }; let dir_entry = entry.insert(DirEntry::File(BlockRecord { inode, frags: HashMap::new(), })); match dir_entry { DirEntry::File(record) => Ok(record), _ => Err(Error::custom("DirEntry was not the File variant")), } } } impl Default for Directory { fn default() -> Self { Self::new() } } /// Keeps track of which principal is storing a fragment. #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FragmentRecord { /// The fragment serial number this record is for. serial: FragmentSerial, /// The principal who is storing this fragment. stored_by: Principal, } impl FragmentRecord { /// Creates a new `FragmentRecord` whose `serial` and `stored_by` fields are set to /// the given values. pub fn new(serial: u32, stored_by: VarHash) -> FragmentRecord { FragmentRecord { serial: FragmentSerial(serial), stored_by: Principal(stored_by), } } } /// An identifier for a security principal, which is any entity that can be authenticated. #[derive( Debug, PartialEq, Eq, Serialize, Deserialize, Hashable, Clone, Default, PartialOrd, Ord, )] pub struct Principal(VarHash); impl Principal { fn kind(&self) -> HashKind { HashKind::from(&self.0) } } impl Display for Principal { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } /// Trait for types which are owned by a `Principal`. pub trait Principaled { /// Returns the `Principal` that owns `self`, using the given hash algorithm. fn principal_of_kind(&self, kind: HashKind) -> Principal; /// Returns the `Principal` that owns `self`, using the default hash algorithm. fn principal(&self) -> Principal { self.principal_of_kind(HashKind::default()) } } /// 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, Hash)] pub struct Epoch(u64); impl Epoch { /// Returns the current epoch time. 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 for Epoch { fn from(value: u64) -> Self { Self(value) } } impl From for u64 { fn from(value: Epoch) -> Self { value.0 } } impl Add for Epoch { type Output = Self; fn add(self, other: Duration) -> Self { Epoch(self.0 + other.as_secs()) } } impl Sub for Epoch { type Output = Self; fn sub(self, other: Duration) -> Self { Epoch(self.0 - other.as_secs()) } } /// The serial number of a block fragment. #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable)] pub struct FragmentSerial(u32); /// A struct which may contain a closure. When this struct is dropped, if it contains a closure /// then that closure is called. This closure can be removed by calling `disarm`. pub struct DropTrigger(Option); impl DropTrigger { pub fn new(trigger: F) -> DropTrigger { DropTrigger(Some(trigger)) } pub fn disarm(&mut self) { self.0.take(); } } impl Drop for DropTrigger { fn drop(&mut self) { if let Some(trigger) = self.0.take() { trigger() } } } #[cfg(feature = "testing")] #[cfg(test)] mod tests { use std::{fs::OpenOptions, io::Cursor, path::PathBuf}; use crate::crypto::{ tpm::{TpmCredStore, TpmCreds}, ConcreteCreds, CredStore, CredsPriv, }; use super::*; use btserde::{from_vec, to_vec}; use tempdir::TempDir; use test_helpers::*; #[test] fn brotli_compress_decompress() { const SECT_SZ: usize = SECTOR_SZ_DEFAULT; const SECT_CT: usize = 16; let params = BrotliParams::new(SECT_SZ, 8, 20); let mut memory = Cursor::new([0u8; SECT_SZ * SECT_CT]); { let write: CompressorWriter<_> = params .clone() .try_compose(&mut memory) .expect("compose for write failed"); write_fill(write, SECT_SZ, SECT_CT); } memory.seek(SeekFrom::Start(0)).expect("seek failed"); { let read: Decompressor<_> = params .try_compose(&mut memory) .expect("compose for read failed"); read_check(read, SECT_SZ, SECT_CT); } } #[test] fn block_can_create_empty() { let harness = SwtpmHarness::new().expect("failed to start swtpm"); let context = harness.context().expect("failed to retrieve context"); let cred_store = TpmCredStore::new(context, harness.state_path()) .expect("failed to create TpmCredStore"); let creds = cred_store.node_creds().expect("failed to get node creds"); BlockOpenOptions::new() .with_inner(BtCursor::new(Vec::::new())) .with_creds(creds) .with_encrypt(true) .open() .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::(&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, _swtpm: SwtpmHarness, temp_dir: TempDir, } 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()) .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 writecap = root_creds .issue_writecap( node_creds.principal(), vec!["nodes".to_string(), "phone".to_string()], Epoch::now() + Duration::from_secs(3600), ) .expect("failed to issue writecap"); node_creds.set_writecap(writecap); BlockTestCase { temp_dir, _swtpm: swtpm, node_creds, root_creds, } } fn fs_path(&self, path: &crate::BlockPath) -> PathBuf { let mut fs_path = self.temp_dir.path().to_owned(); fs_path.extend(path.components()); fs_path } fn open_new(&mut self, path: &crate::BlockPath) -> Box { let file = OpenOptions::new() .create_new(true) .read(true) .write(true) .open(&self.fs_path(path)) .expect("failed to open file"); let mut block = BlockOpenOptions::new() .with_inner(file) .with_creds(self.node_creds.clone()) .with_encrypt(true) .open() .expect("failed to open block"); block .mut_meta_body() .set_path(self.node_creds.writecap().unwrap().body.path.clone()); block } fn open_existing(&mut self, path: &crate::BlockPath) -> Box { let file = OpenOptions::new() .read(true) .write(true) .open(&self.fs_path(path)) .expect("failed to reopen file"); BlockOpenOptions::new() .with_inner(file) .with_creds(self.node_creds.clone()) .with_encrypt(true) .open() .expect("failed to reopen block") } } #[test] fn block_contents_persisted() { const EXPECTED: &[u8] = b"Silly sordid sulking sultans."; let mut case = BlockTestCase::new(); let path = BlockPath::new(case.root_creds.principal(), vec!["test.blk".to_string()]); { let mut block = case.open_new(&path); block.write(EXPECTED).expect("failed to write"); block.flush().expect("flush failed"); } let mut block = case.open_existing(&path); let mut actual = [0u8; EXPECTED.len()]; block.read(&mut actual).expect("read failed"); assert_eq!(EXPECTED, actual); } #[test] fn block_write_twice() { const EXPECTED: &[u8] = b"Cool callous calamitous colonels."; const MID: usize = EXPECTED.len() / 2; let mut case = BlockTestCase::new(); let path = BlockPath::new(case.root_creds.principal(), vec!["test.blk".to_string()]); { let mut block = case.open_new(&path); block.write(&EXPECTED[..MID]).expect("first write failed"); block.flush().expect("first flush failed"); } { let mut block = case.open_existing(&path); block .seek(SeekFrom::Start(MID.try_into().unwrap())) .expect("seek failed"); block.write(&EXPECTED[MID..]).expect("second write failed"); block.flush().expect("second flush failed"); } { let mut block = case.open_existing(&path); let mut actual = [0u8; EXPECTED.len()]; block.read(&mut actual).expect("read failed"); assert_eq!(EXPECTED, actual); } } #[test] fn block_write_with_different_creds() { const EXPECTED: &[u8] = b"Cool callous calamitous colonels."; const MID: usize = EXPECTED.len() / 2; let mut case = BlockTestCase::new(); let path = BlockPath::new(case.root_creds.principal(), vec!["test.blk".to_string()]); let app_creds = { let mut app_creds = ConcreteCreds::generate().expect("failed to generate app creds"); let writecap = case .root_creds .issue_writecap( app_creds.principal(), path.components().map(|e| e.to_string()).collect(), Epoch::now() + Duration::from_secs(60), ) .expect("failed to issue writecap"); app_creds.set_writecap(writecap); app_creds }; { 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"); block.flush().expect("first flush failed"); } { let file = OpenOptions::new() .read(true) .write(true) .open(case.fs_path(&path)) .expect("failed to reopen file"); let mut block = BlockOpenOptions::new() .with_inner(file) // Note that this write is performed using app_creds. .with_creds(app_creds) .with_encrypt(true) .open() .expect("failed to reopen block"); block .seek(SeekFrom::Start(MID.try_into().unwrap())) .expect("seek failed"); block.write(&EXPECTED[MID..]).expect("second write failed"); block.flush().expect("second flush failed"); } { let file = OpenOptions::new() .read(true) .write(true) .open(case.fs_path(&path)) .expect("failed to reopen file"); let mut block = BlockOpenOptions::new() .with_inner(file) .with_creds(case.node_creds) .with_encrypt(true) .open() .expect("failed to reopen block"); let mut actual = [0u8; EXPECTED.len()]; block.read(&mut actual).expect("read failed"); assert_eq!(EXPECTED, actual); } } }