|
@@ -25,7 +25,7 @@ use log::{error, warn};
|
|
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
|
use serde_big_array::BigArray;
|
|
|
use std::{
|
|
|
- collections::HashMap,
|
|
|
+ collections::{BTreeMap, HashMap},
|
|
|
convert::{Infallible, TryFrom},
|
|
|
fmt::{self, Display, Formatter},
|
|
|
hash::Hash as Hashable,
|
|
@@ -223,7 +223,7 @@ impl<T: Read> ReadExt for T {}
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
|
|
pub struct BlockMetaBody {
|
|
|
path: Path,
|
|
|
- readcaps: HashMap<Principal, Ciphertext<SymKey>>,
|
|
|
+ readcaps: BTreeMap<Principal, Ciphertext<SymKey>>,
|
|
|
writecap: Option<Writecap>,
|
|
|
/// A hash which provides integrity for the contents of the block body.
|
|
|
integrity: Option<Hash>,
|
|
@@ -235,7 +235,7 @@ impl BlockMetaBody {
|
|
|
fn new<C: Creds>(creds: &C) -> BlockMetaBody {
|
|
|
BlockMetaBody {
|
|
|
path: Path::default(),
|
|
|
- readcaps: HashMap::new(),
|
|
|
+ readcaps: BTreeMap::new(),
|
|
|
writecap: creds.writecap().map(|e| e.to_owned()),
|
|
|
integrity: None,
|
|
|
signing_key: creds.public_sign().to_owned(),
|
|
@@ -269,8 +269,11 @@ impl<T: Read + Seek, C: Creds> BlockStream<T, C> {
|
|
|
fn new(inner: T, creds: C) -> Result<BlockStream<T, C>> {
|
|
|
let (trailered, trailer) = Trailered::<_, BlockMeta>::new(inner)?;
|
|
|
let trailer = match trailer {
|
|
|
- Some(trailer) => {
|
|
|
- crypto::verify_header(&trailer.body, &trailer.sig)?;
|
|
|
+ Some(mut trailer) => {
|
|
|
+ trailer.assert_valid()?;
|
|
|
+ // We need to use the writecap and signing_key provided by the current credentials.
|
|
|
+ trailer.body.writecap = creds.writecap().map(|e| e.to_owned());
|
|
|
+ trailer.body.signing_key = creds.public_sign().to_owned();
|
|
|
trailer
|
|
|
}
|
|
|
None => {
|
|
@@ -1001,7 +1004,9 @@ impl FragmentRecord {
|
|
|
}
|
|
|
|
|
|
/// An identifier for a security principal, which is any entity that can be authenticated.
|
|
|
-#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable, Clone, Default)]
|
|
|
+#[derive(
|
|
|
+ Debug, PartialEq, Eq, Serialize, Deserialize, Hashable, Clone, Default, PartialOrd, Ord,
|
|
|
+)]
|
|
|
pub struct Principal(Hash);
|
|
|
|
|
|
impl Principal {
|
|
@@ -1010,6 +1015,12 @@ impl Principal {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+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`.
|
|
|
trait Principaled {
|
|
|
/// Returns the `Principal` that owns `self`, using the given hash algorithm.
|
|
@@ -1207,9 +1218,12 @@ struct FragmentSerial(u32);
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
- use std::{fs::OpenOptions, io::Cursor};
|
|
|
+ use std::{fs::OpenOptions, io::Cursor, path::PathBuf};
|
|
|
|
|
|
- use crate::crypto::{tpm::TpmCredStore, CredStore, CredsPriv};
|
|
|
+ use crate::crypto::{
|
|
|
+ tpm::{TpmCredStore, TpmCreds},
|
|
|
+ ConcreteCreds, CredStore, CredsPriv,
|
|
|
+ };
|
|
|
|
|
|
use super::*;
|
|
|
use tempdir::TempDir;
|
|
@@ -1678,57 +1692,199 @@ mod tests {
|
|
|
.expect("failed to open block");
|
|
|
}
|
|
|
|
|
|
- #[test]
|
|
|
- fn block_contents_persisted() {
|
|
|
- const EXPECTED: &[u8] = b"Silly sordid sulking sultans.";
|
|
|
- let temp_dir = TempDir::new("btlib").expect("failed to create temp dir");
|
|
|
- let file_path = temp_dir.path().join("test.blk").to_owned();
|
|
|
- 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 root_creds = cred_store
|
|
|
- .gen_root_creds("(1337Prestidigitation7331)")
|
|
|
- .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");
|
|
|
- let path = writecap.path.clone();
|
|
|
- node_creds.set_writecap(writecap);
|
|
|
- {
|
|
|
+ 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,
|
|
|
+ node_creds,
|
|
|
+ root_creds,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn fs_path(&self, path: &crate::Path) -> PathBuf {
|
|
|
+ let mut fs_path = self.temp_dir.path().to_owned();
|
|
|
+ fs_path.extend(path.components.iter());
|
|
|
+ fs_path
|
|
|
+ }
|
|
|
+
|
|
|
+ fn open_new(&mut self, path: &crate::Path) -> Box<dyn Block> {
|
|
|
let file = OpenOptions::new()
|
|
|
.create_new(true)
|
|
|
- .write(true)
|
|
|
.read(true)
|
|
|
- .open(&file_path)
|
|
|
+ .write(true)
|
|
|
+ .open(&self.fs_path(path))
|
|
|
.expect("failed to open file");
|
|
|
let mut block = BlockOpenOptions::new()
|
|
|
.with_inner(file)
|
|
|
- .with_creds(node_creds.clone())
|
|
|
+ .with_creds(self.node_creds.clone())
|
|
|
.with_encrypt(true)
|
|
|
.open()
|
|
|
.expect("failed to open block");
|
|
|
- block.set_path(path);
|
|
|
+ block.set_path(self.node_creds.writecap().unwrap().path.clone());
|
|
|
+ block
|
|
|
+ }
|
|
|
+
|
|
|
+ fn open_existing(&mut self, path: &crate::Path) -> Box<dyn Block> {
|
|
|
+ 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.";
|
|
|
+ const BLOCK_NAME: &'static str = "test.blk";
|
|
|
+
|
|
|
+ let mut case = BlockTestCase::new();
|
|
|
+ let path = Path {
|
|
|
+ root: case.root_creds.principal(),
|
|
|
+ components: 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 file = OpenOptions::new()
|
|
|
- .read(true)
|
|
|
- .open(&file_path)
|
|
|
- .expect("failed to reopen file");
|
|
|
- let mut block = BlockOpenOptions::new()
|
|
|
- .with_inner(file)
|
|
|
- .with_creds(node_creds)
|
|
|
- .with_encrypt(true)
|
|
|
- .open()
|
|
|
- .expect("failed to reopen block");
|
|
|
+ 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;
|
|
|
+ const BLOCK_NAME: &'static str = "test.blk";
|
|
|
+
|
|
|
+ let mut case = BlockTestCase::new();
|
|
|
+ let path = Path {
|
|
|
+ root: case.root_creds.principal(),
|
|
|
+ components: 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;
|
|
|
+ const BLOCK_NAME: &'static str = "test.blk";
|
|
|
+
|
|
|
+ let mut case = BlockTestCase::new();
|
|
|
+ let path = Path {
|
|
|
+ root: case.root_creds.principal(),
|
|
|
+ components: 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.clone(),
|
|
|
+ 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
|
|
|
+ .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);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|