|
@@ -9,6 +9,7 @@ pub mod crypto;
|
|
|
pub mod msg;
|
|
|
mod sectored_buf;
|
|
|
mod trailered;
|
|
|
+pub mod btlog;
|
|
|
|
|
|
#[cfg(feature = "testing")]
|
|
|
pub mod test_helpers;
|
|
@@ -55,6 +56,9 @@ pub enum Error {
|
|
|
Crypto(crypto::Error),
|
|
|
IncorrectSize { expected: usize, actual: usize },
|
|
|
NoBlockKey,
|
|
|
+ NotOpen(u64),
|
|
|
+ NoBlockPath,
|
|
|
+ InodeNotFound(u64),
|
|
|
Custom(Box<dyn std::fmt::Debug + Send + Sync>),
|
|
|
}
|
|
|
|
|
@@ -75,6 +79,9 @@ impl Display for Error {
|
|
|
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::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),
|
|
|
}
|
|
|
}
|
|
@@ -343,7 +350,6 @@ impl From<&BlockMetaSecrets> for Attr {
|
|
|
/// 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<Ciphertext<SymKey>>,
|
|
@@ -356,6 +362,9 @@ pub struct BlockMetaBody {
|
|
|
/// Additional metadata which is subject to confidentiality protection.
|
|
|
secrets: Ciphertext<BlockMetaSecrets>,
|
|
|
|
|
|
+ #[serde(skip)]
|
|
|
+ /// The path in the blocktree where this block is located.
|
|
|
+ path: BlockPath,
|
|
|
#[serde(skip)]
|
|
|
/// The cleartext block key.
|
|
|
block_key: Option<SymKey>,
|
|
@@ -458,7 +467,7 @@ impl BlockMetaBody {
|
|
|
}
|
|
|
|
|
|
/// Signed metadata associated with a block.
|
|
|
-#[derive(Serialize, Deserialize)]
|
|
|
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
|
pub struct BlockMeta {
|
|
|
body: BlockMetaBody,
|
|
|
sig: Signature,
|
|
@@ -500,21 +509,25 @@ struct BlockStream<T, C> {
|
|
|
}
|
|
|
|
|
|
impl<T: Read + Seek, C: Creds> BlockStream<T, C> {
|
|
|
- fn new(inner: T, creds: C) -> Result<BlockStream<T, C>> {
|
|
|
+ fn new(inner: T, creds: C, block_path: BlockPath) -> Result<BlockStream<T, C>> {
|
|
|
let (trailered, meta) = Trailered::<_, BlockMeta>::new(inner)?;
|
|
|
let meta = match meta {
|
|
|
Some(mut meta) => {
|
|
|
- meta.assert_valid()?;
|
|
|
+ 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 = creds.writecap().map(|e| e.to_owned());
|
|
|
+ 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.mut_body().add_readcap_for(creds.principal(), &creds)?;
|
|
|
- meta.body.writecap = creds.writecap().map(|e| e.to_owned());
|
|
|
+ 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
|
|
|
}
|
|
|
};
|
|
@@ -527,7 +540,7 @@ impl<T: Read + Seek, C: Creds> BlockStream<T, C> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-impl<T: Write + Seek, C: Signer> Write for BlockStream<T, C> {
|
|
|
+impl<T: Write + Seek, C: Signer + Principaled + Decrypter> Write for BlockStream<T, C> {
|
|
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
|
self.trailered.write(buf)
|
|
|
}
|
|
@@ -540,7 +553,7 @@ impl<T: Write + Seek, C: Signer> Write for BlockStream<T, C> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-impl<T: Write + Seek, C: Signer> WriteInteg for BlockStream<T, C> {
|
|
|
+impl<T: Write + Seek, C: Signer + Principaled + Decrypter> WriteInteg for BlockStream<T, C> {
|
|
|
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);
|
|
@@ -620,6 +633,7 @@ pub struct BlockOpenOptions<T, C> {
|
|
|
creds: C,
|
|
|
encrypt: bool,
|
|
|
compress: bool,
|
|
|
+ block_path: Option<BlockPath>,
|
|
|
}
|
|
|
|
|
|
impl BlockOpenOptions<(), ()> {
|
|
@@ -629,6 +643,7 @@ impl BlockOpenOptions<(), ()> {
|
|
|
creds: (),
|
|
|
encrypt: true,
|
|
|
compress: true,
|
|
|
+ block_path: Default::default(),
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -640,6 +655,7 @@ impl<T, C> BlockOpenOptions<T, C> {
|
|
|
creds: self.creds,
|
|
|
encrypt: self.encrypt,
|
|
|
compress: self.compress,
|
|
|
+ block_path: self.block_path,
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -649,6 +665,7 @@ impl<T, C> BlockOpenOptions<T, C> {
|
|
|
creds,
|
|
|
encrypt: self.encrypt,
|
|
|
compress: self.compress,
|
|
|
+ block_path: self.block_path,
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -661,13 +678,19 @@ impl<T, C> BlockOpenOptions<T, C> {
|
|
|
self.compress = compress;
|
|
|
self
|
|
|
}
|
|
|
+
|
|
|
+ pub fn with_block_path(mut self, block_path: BlockPath) -> Self {
|
|
|
+ self.block_path = Some(block_path);
|
|
|
+ self
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl<T: Read + Write + Seek + FileReadWriteVolatile + 'static, C: Creds + 'static>
|
|
|
BlockOpenOptions<T, C>
|
|
|
{
|
|
|
pub fn open(self) -> Result<Box<dyn Block>> {
|
|
|
- let stream = BlockStream::new(self.inner, self.creds)?;
|
|
|
+ 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()?;
|
|
@@ -782,6 +805,27 @@ pub struct Writecap {
|
|
|
next: Option<Box<Writecap>>,
|
|
|
}
|
|
|
|
|
|
+impl Writecap {
|
|
|
+ /// Returns the root key that was used to sign this writecap.
|
|
|
+ pub fn root_signing_key(&self) -> &AsymKeyPub<Sign> {
|
|
|
+ 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)]
|
|
@@ -838,6 +882,24 @@ pub enum DirEntry {
|
|
|
Server(ServerRecord),
|
|
|
}
|
|
|
|
|
|
+impl DirEntry {
|
|
|
+ pub fn inode(&self) -> Option<u64> {
|
|
|
+ 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 {
|
|
@@ -871,6 +933,10 @@ impl Directory {
|
|
|
_ => Err(Error::custom("DirEntry was not the File variant")),
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ pub fn entries(&self) -> impl Iterator<Item = (&str, &DirEntry)> {
|
|
|
+ self.entries.iter().map(|kv| (kv.0.as_str(), kv.1))
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl Default for Directory {
|
|
@@ -1041,21 +1107,6 @@ mod tests {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- #[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::<u8>::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]
|
|
@@ -1087,6 +1138,8 @@ mod tests {
|
|
|
node_creds: TpmCreds,
|
|
|
_swtpm: SwtpmHarness,
|
|
|
temp_dir: TempDir,
|
|
|
+ root_path: BlockPath,
|
|
|
+ node_path: BlockPath,
|
|
|
}
|
|
|
|
|
|
impl BlockTestCase {
|
|
@@ -1096,26 +1149,34 @@ mod tests {
|
|
|
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())
|
|
|
+ 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(),
|
|
|
- vec!["nodes".to_string(), "phone".to_string()],
|
|
|
+ components.clone(),
|
|
|
Epoch::now() + Duration::from_secs(3600),
|
|
|
)
|
|
|
.expect("failed to issue writecap");
|
|
|
- node_creds.set_writecap(writecap);
|
|
|
- BlockTestCase {
|
|
|
+ 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 {
|
|
@@ -1124,17 +1185,18 @@ mod tests {
|
|
|
fs_path
|
|
|
}
|
|
|
|
|
|
- fn open_new(&mut self, path: &crate::BlockPath) -> Box<dyn Block> {
|
|
|
+ fn open_new(&mut self, path: crate::BlockPath) -> Box<dyn Block> {
|
|
|
let file = OpenOptions::new()
|
|
|
.create_new(true)
|
|
|
.read(true)
|
|
|
.write(true)
|
|
|
- .open(&self.fs_path(path))
|
|
|
+ .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
|
|
@@ -1143,19 +1205,39 @@ mod tests {
|
|
|
block
|
|
|
}
|
|
|
|
|
|
- fn open_existing(&mut self, path: &crate::BlockPath) -> Box<dyn Block> {
|
|
|
+ fn open_existing(&mut self, path: crate::BlockPath) -> Box<dyn Block> {
|
|
|
let file = OpenOptions::new()
|
|
|
.read(true)
|
|
|
.write(true)
|
|
|
- .open(&self.fs_path(path))
|
|
|
+ .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::<u8>::new()))
|
|
|
+ .with_creds(case.node_creds)
|
|
|
+ .with_encrypt(true)
|
|
|
+ .with_block_path(case.root_path)
|
|
|
+ .open()
|
|
|
+ .expect("failed to open block");
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
@@ -1163,13 +1245,13 @@ mod tests {
|
|
|
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 path = case.node_path("test.blk");
|
|
|
{
|
|
|
- let mut block = case.open_new(&path);
|
|
|
+ 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 block = case.open_existing(path);
|
|
|
let mut actual = [0u8; EXPECTED.len()];
|
|
|
block.read(&mut actual).expect("read failed");
|
|
|
assert_eq!(EXPECTED, actual);
|
|
@@ -1181,14 +1263,14 @@ mod tests {
|
|
|
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 path = case.node_path("test.blk");
|
|
|
{
|
|
|
- let mut block = case.open_new(&path);
|
|
|
+ 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);
|
|
|
+ let mut block = case.open_existing(path.clone());
|
|
|
block
|
|
|
.seek(SeekFrom::Start(MID.try_into().unwrap()))
|
|
|
.expect("seek failed");
|
|
@@ -1196,7 +1278,7 @@ mod tests {
|
|
|
block.flush().expect("second flush failed");
|
|
|
}
|
|
|
{
|
|
|
- let mut block = case.open_existing(&path);
|
|
|
+ let mut block = case.open_existing(path);
|
|
|
let mut actual = [0u8; EXPECTED.len()];
|
|
|
block.read(&mut actual).expect("read failed");
|
|
|
assert_eq!(EXPECTED, actual);
|
|
@@ -1209,7 +1291,7 @@ mod tests {
|
|
|
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 path = case.node_path("test.blk");
|
|
|
let app_creds = {
|
|
|
let mut app_creds = ConcreteCreds::generate().expect("failed to generate app creds");
|
|
|
let writecap = case
|
|
@@ -1224,7 +1306,7 @@ mod tests {
|
|
|
app_creds
|
|
|
};
|
|
|
{
|
|
|
- let mut block = case.open_new(&path);
|
|
|
+ let mut block = case.open_new(path.clone());
|
|
|
block
|
|
|
.mut_meta_body()
|
|
|
.add_readcap_for(app_creds.principal(), &app_creds)
|
|
@@ -1243,6 +1325,7 @@ mod tests {
|
|
|
// 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
|
|
@@ -1261,6 +1344,7 @@ mod tests {
|
|
|
.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()];
|