pub use block_path::{BlockPath, BlockPathError}; pub mod blocktree; /// Code which enables cryptographic operations. pub mod crypto; pub mod log; pub mod msg; mod block_path; mod sectored_buf; #[cfg(test)] mod test_helpers; mod trailered; #[macro_use] extern crate static_assertions; #[macro_use] #[cfg(test)] extern crate lazy_static; use btserde::{read_from, write_to}; use crypto::{ AsymKeyPub, Ciphertext, Creds, Decrypter, DecrypterExt, Encrypter, EncrypterExt, HashKind, MerkleStream, PublicCreds, SecretStream, Sign, Signature, Signer, SymKey, SymKeyKind, VarHash, }; use trailered::Trailered; use ::log::error; use brotli::{CompressorWriter, Decompressor}; use fuse_backend_rs::abi::fuse_abi::{stat64, Attr}; 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 strum_macros::{Display, EnumDiscriminants, FromRepr}; #[derive(Debug)] pub enum Error { MissingWritecap, Io(std::io::Error), Serde(btserde::Error), Crypto(crypto::Error), IncorrectSize { expected: usize, actual: usize }, NoBlockKey, NotOpen(u64), InvalidHandle { handle: u64, inode: u64 }, NoHandlesAvailable(u64), NoBlockPath, InodeNotFound(u64), 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::NotOpen(inode) => write!(f, "inode {inode} is not open"), Error::InvalidHandle { handle, inode } => { write!(f, "invalid handle {handle} for inode {inode}") } Error::NoHandlesAvailable(inode) => { write!(f, "no handles are available for inode {inode}") } Error::NoBlockPath => write!(f, "no block path was specified"), Error::InodeNotFound(inode) => write!(f, "inode {inode} could not be found"), 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)) } } trait ToStringInIoErr { fn err_to_string(self) -> std::result::Result; } impl ToStringInIoErr for std::result::Result { fn err_to_string(self) -> std::result::Result { self.map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string())) } } /// 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; /// ### THE BLOCK TRAIT /// /// Trait for types which provide read and write access to blocks. pub trait Block: Read + Write + Seek + MetaAccess { /// Flushes metadata to persistent storage. fn flush_meta(&mut self) -> Result<()>; fn read_dir(&mut self) -> Result { self.seek(SeekFrom::Start(0))?; // &mut &mut Self has to be passed because rustc complains that &mut Self is not sized, // for some reason (implicit dereference?). You can't pass in `&mut self` because `self` // is not declared as mutable. Hence this hack. let mut selfie = self; let dir: Directory = read_from(&mut selfie)?; Ok(dir) } fn write_dir(&mut self, dir: &Directory) -> Result<()> { self.seek(SeekFrom::Start(0))?; let mut selfie = self; write_to(dir, &mut selfie)?; selfie.flush()?; Ok(()) } } impl MetaAccess for &mut T {} impl Block for &mut T { fn flush_meta(&mut self) -> Result<()> { (*self).flush_meta() } } // 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: AsRef + AsMut { fn meta(&self) -> &BlockMeta { self.as_ref() } fn mut_meta(&mut self) -> &mut BlockMeta { self.as_mut() } fn meta_body(&self) -> &BlockMetaBody { self.meta().body() } fn mut_meta_body(&mut self) -> &mut BlockMetaBody { self.mut_meta().mut_body() } } impl AsRef for &BlockMeta { fn as_ref(&self) -> &BlockMeta { self } } impl AsRef for &mut BlockMeta { fn as_ref(&self) -> &BlockMeta { self } } impl AsMut for &mut BlockMeta { fn as_mut(&mut self) -> &mut BlockMeta { self } } impl MetaAccess for &mut BlockMeta {} /// 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 [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 { /// 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 path in the blocktree where this block is located. This is initialized in /// BlockStream::new. path: BlockPath, #[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)?); let secrets = BlockMetaSecrets::default(); 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(&secrets)?, block_key: Some(block_key), secrets_struct: Some(secrets), }) } 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(Debug, PartialEq, 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, block_path: BlockPath) -> Result> { let (trailered, meta) = Trailered::<_, BlockMeta>::new(inner)?; let meta = match meta { Some(mut meta) => { meta.assert_valid(&block_path)?; meta.body.path = block_path; // 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 } None => { let mut meta = BlockMeta::new(&creds)?; meta.body.path = block_path; meta.body.add_readcap_for(creds.principal(), &creds)?; meta.body.writecap = Some(creds.writecap().ok_or(Error::MissingWritecap)?.to_owned()); meta } }; Ok(BlockStream { trailered, meta, meta_body_buf: Vec::new(), creds, }) } } impl BlockStream { fn sign_flush_meta(&mut self) -> io::Result<()> { self.meta_body_buf.clear(); self.meta.sig = self .creds .ser_sign_into(&self.meta.body, &mut self.meta_body_buf)?; self.trailered.flush(&self.meta) } } 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.sign_flush_meta() } } 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 AsRef for BlockStream { fn as_ref(&self) -> &BlockMeta { &self.meta } } impl AsMut for BlockStream { fn as_mut(&mut self) -> &mut BlockMeta { &mut self.meta } } impl MetaAccess for BlockStream {} impl Block for BlockStream { fn flush_meta(&mut self) -> Result<()> { self.sign_flush_meta().map_err(|err| err.into()) } } pub struct BlockOpenOptions { inner: T, creds: C, encrypt: bool, compress: bool, block_path: Option, } impl BlockOpenOptions<(), ()> { pub fn new() -> BlockOpenOptions<(), ()> { BlockOpenOptions { inner: (), creds: (), encrypt: true, compress: true, block_path: Default::default(), } } } impl BlockOpenOptions { pub fn with_inner(self, inner: U) -> BlockOpenOptions { BlockOpenOptions { inner, creds: self.creds, encrypt: self.encrypt, compress: self.compress, block_path: self.block_path, } } pub fn with_creds(self, creds: D) -> BlockOpenOptions { BlockOpenOptions { inner: self.inner, creds, encrypt: self.encrypt, compress: self.compress, block_path: self.block_path, } } 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 } pub fn with_block_path(mut self, block_path: BlockPath) -> Self { self.block_path = Some(block_path); self } } impl BlockOpenOptions { pub fn open(self) -> Result> { let block_path = self.block_path.ok_or(Error::NoBlockPath)?; let stream = BlockStream::new(self.inner, self.creds, block_path)?; 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>, } impl Writecap { /// Returns the root key that was used to sign this writecap. pub fn root_signing_key(&self) -> &AsymKeyPub { let mut writecap = self; while writecap.next.is_some() { writecap = writecap.next.as_ref().unwrap(); } &writecap.body.signing_key } /// Returns the principal of the root key which was used to sign this writecap. pub fn root_principal(&self) -> Principal { self.root_signing_key().principal() } /// Returns the path to the root block of the blocktree that the root principal owns. pub fn root_block_path(&self) -> BlockPath { BlockPath::new(self.root_principal(), vec![]) } } /// 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? /// TODO: Move this into the block metadata. pub frags: HashMap, } impl BlockRecord { pub fn new(inode: u64) -> BlockRecord { BlockRecord { inode, frags: HashMap::new(), } } } #[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, EnumDiscriminants)] #[strum_discriminants(derive(FromRepr, Display, Serialize, Deserialize))] #[strum_discriminants(name(DirEntryKind))] pub enum DirEntry { Directory(BlockRecord), File(BlockRecord), Server(ServerRecord), } impl DirEntry { pub fn inode(&self) -> Option { match self { Self::Directory(record) => Some(record.inode), Self::File(record) => Some(record.inode), Self::Server(..) => None, } } pub fn kind(&self) -> u8 { match self { Self::Directory(..) => libc::DT_DIR, Self::File(..) => libc::DT_REG, Self::Server(..) => libc::DT_UNKNOWN, } } } /// 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")), } } pub fn entries(&self) -> impl Iterator { self.entries .iter() .map(|(name, entry)| (name.as_str(), entry)) } } 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()) } } impl Principaled for &T { fn principal_of_kind(&self, kind: HashKind) -> Principal { (*self).principal_of_kind(kind) } } /// 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(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 swtpm_harness::SwtpmHarness; 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); } } /// 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, root_path: BlockPath, node_path: BlockPath, } 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 components = vec!["nodes".to_string(), "phone".to_string()]; let writecap = root_creds .issue_writecap( node_creds.principal(), components.clone(), 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"); 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![]), root_creds, }; std::fs::create_dir_all(case.fs_path(&case.node_path)) .expect("failed to create node path"); case } 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) .with_block_path(path) .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) .with_block_path(path) .open() .expect("failed to reopen block") } /// Returns a path in the directory which the node creds have permission to write to. fn node_path(&self, file_name: &str) -> BlockPath { let mut path = self.node_path.clone(); path.push_component(file_name.to_owned()); path } } #[test] fn block_can_create_empty() { let case = BlockTestCase::new(); BlockOpenOptions::new() .with_inner(BtCursor::new(Vec::::new())) .with_creds(case.node_creds) .with_encrypt(true) .with_block_path(case.root_path) .open() .expect("failed to open block"); } #[test] fn block_contents_persisted() { const EXPECTED: &[u8] = b"Silly sordid sulking sultans."; let mut case = BlockTestCase::new(); let path = case.node_path("test.blk"); { let mut block = case.open_new(path.clone()); 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 = case.node_path("test.blk"); { let mut block = case.open_new(path.clone()); block.write(&EXPECTED[..MID]).expect("first write failed"); block.flush().expect("first flush failed"); } { let mut block = case.open_existing(path.clone()); 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 = case.node_path("test.blk"); 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.clone()); 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) .with_block_path(path.clone()) .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) .with_block_path(path) .open() .expect("failed to reopen block"); let mut actual = [0u8; EXPECTED.len()]; block.read(&mut actual).expect("read failed"); assert_eq!(EXPECTED, actual); } } }