|
@@ -19,11 +19,11 @@ use brotli::{CompressorWriter, Decompressor};
|
|
use btserde::{self, read_from, write_to};
|
|
use btserde::{self, read_from, write_to};
|
|
mod crypto;
|
|
mod crypto;
|
|
use crypto::{
|
|
use crypto::{
|
|
- AsymKeyPub, Ciphertext, CredsPriv, Decrypter, Encrypter, EncrypterExt, Hash, HashKind,
|
|
|
|
- MerkleStream, SecretStream, Sign, Signature, Signer, SymKey,
|
|
|
|
|
|
+ AsymKeyPub, Ciphertext, Creds, Decrypter, Encrypter, EncrypterExt, Hash, HashKind,
|
|
|
|
+ MerkleStream, SecretStream, Sign, Signature, Signer, SymKey, SymKeyKind,
|
|
};
|
|
};
|
|
|
|
|
|
-use log::error;
|
|
|
|
|
|
+use log::{error, warn};
|
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
use serde_big_array::BigArray;
|
|
use serde_big_array::BigArray;
|
|
use std::{
|
|
use std::{
|
|
@@ -40,7 +40,7 @@ use std::{
|
|
};
|
|
};
|
|
|
|
|
|
#[derive(Debug)]
|
|
#[derive(Debug)]
|
|
-enum Error {
|
|
|
|
|
|
+pub enum Error {
|
|
MissingWritecap,
|
|
MissingWritecap,
|
|
Io(std::io::Error),
|
|
Io(std::io::Error),
|
|
Serde(btserde::Error),
|
|
Serde(btserde::Error),
|
|
@@ -177,9 +177,11 @@ impl<T, U: Decompose<T>, S: TryCompose<T, U, Error = Infallible>> Compose<T, U>
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-trait HeaderAccess {
|
|
|
|
|
|
+pub trait HeaderAccess {
|
|
fn block_key(&self) -> Result<SymKey>;
|
|
fn block_key(&self) -> Result<SymKey>;
|
|
fn add_readcap_for(&mut self, owner: Principal, key: &dyn Encrypter) -> Result<()>;
|
|
fn add_readcap_for(&mut self, owner: Principal, key: &dyn Encrypter) -> Result<()>;
|
|
|
|
+ /// Returns the integrity value used to protect the contents of the block.
|
|
|
|
+ fn integrity(&self) -> Option<&[u8]>;
|
|
}
|
|
}
|
|
|
|
|
|
/// Extensions to the `Read` trait.
|
|
/// Extensions to the `Read` trait.
|
|
@@ -221,7 +223,7 @@ pub struct Header {
|
|
readcaps: HashMap<Principal, Ciphertext<SymKey>>,
|
|
readcaps: HashMap<Principal, Ciphertext<SymKey>>,
|
|
writecap: Option<Writecap>,
|
|
writecap: Option<Writecap>,
|
|
/// A hash which provides integrity for the contents of the block body.
|
|
/// A hash which provides integrity for the contents of the block body.
|
|
- integrity: Hash,
|
|
|
|
|
|
+ integrity: Option<Hash>,
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Default)]
|
|
#[derive(Serialize, Deserialize, Default)]
|
|
@@ -237,19 +239,35 @@ struct BlockStream<T, C> {
|
|
creds: C,
|
|
creds: C,
|
|
}
|
|
}
|
|
|
|
|
|
-impl<T: Read + Seek, C> 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) -> Result<BlockStream<T, C>> {
|
|
- let (trailered, trailer) = Trailered::new(inner)?;
|
|
|
|
|
|
+ let (trailered, trailer) = Trailered::<_, BlockTrailer>::new(inner)?;
|
|
|
|
+ let trailer = match trailer {
|
|
|
|
+ Some(trailer) => {
|
|
|
|
+ crypto::verify_header(&trailer.header, &trailer.sig)?;
|
|
|
|
+ trailer
|
|
|
|
+ }
|
|
|
|
+ None => {
|
|
|
|
+ let mut trailer = BlockTrailer::default();
|
|
|
|
+ let block_key = SymKey::generate(SymKeyKind::default())?;
|
|
|
|
+ trailer
|
|
|
|
+ .header
|
|
|
|
+ .readcaps
|
|
|
|
+ .insert(creds.owner(), creds.ser_encrypt(&block_key)?);
|
|
|
|
+ // TODO: Insert writecap.
|
|
|
|
+ trailer
|
|
|
|
+ }
|
|
|
|
+ };
|
|
Ok(BlockStream {
|
|
Ok(BlockStream {
|
|
trailered,
|
|
trailered,
|
|
- trailer: trailer.unwrap_or_default(),
|
|
|
|
|
|
+ trailer,
|
|
header_buf: Vec::new(),
|
|
header_buf: Vec::new(),
|
|
creds,
|
|
creds,
|
|
})
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-impl<C> BlockStream<File, C> {
|
|
|
|
|
|
+impl<C: Creds> BlockStream<File, C> {
|
|
fn open<P: AsRef<std::path::Path>>(path: P, creds: C) -> Result<BlockStream<File, C>> {
|
|
fn open<P: AsRef<std::path::Path>>(path: P, creds: C) -> Result<BlockStream<File, C>> {
|
|
let inner = OpenOptions::new().read(true).write(true).open(path)?;
|
|
let inner = OpenOptions::new().read(true).write(true).open(path)?;
|
|
BlockStream::new(inner, creds)
|
|
BlockStream::new(inner, creds)
|
|
@@ -272,7 +290,8 @@ 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> WriteInteg for BlockStream<T, C> {
|
|
fn flush_integ(&mut self, integrity: &[u8]) -> io::Result<()> {
|
|
fn flush_integ(&mut self, integrity: &[u8]) -> io::Result<()> {
|
|
let header = &mut self.trailer.header;
|
|
let header = &mut self.trailer.header;
|
|
- header.integrity.as_mut().copy_from_slice(integrity);
|
|
|
|
|
|
+ let integ = header.integrity.get_or_insert_with(Hash::default);
|
|
|
|
+ integ.as_mut().copy_from_slice(integrity);
|
|
self.header_buf.clear();
|
|
self.header_buf.clear();
|
|
write_to(&header, &mut self.header_buf).box_err()?;
|
|
write_to(&header, &mut self.header_buf).box_err()?;
|
|
self.trailer.sig = self
|
|
self.trailer.sig = self
|
|
@@ -314,6 +333,14 @@ impl<T, C: Decrypter + Owned> HeaderAccess for BlockStream<T, C> {
|
|
self.trailer.header.readcaps.insert(owner, readcap);
|
|
self.trailer.header.readcaps.insert(owner, readcap);
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ fn integrity(&self) -> Option<&[u8]> {
|
|
|
|
+ self.trailer
|
|
|
|
+ .header
|
|
|
|
+ .integrity
|
|
|
|
+ .as_ref()
|
|
|
|
+ .map(|hash| hash.as_ref())
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
struct BlockOpenOptions<T, C> {
|
|
struct BlockOpenOptions<T, C> {
|
|
@@ -364,11 +391,12 @@ impl<T, C> BlockOpenOptions<T, C> {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-impl<T: Read + Write + Seek + 'static, C: CredsPriv + Owned + 'static> BlockOpenOptions<T, C> {
|
|
|
|
|
|
+impl<T: Read + Write + Seek + 'static, C: Creds + 'static> BlockOpenOptions<T, C> {
|
|
fn open(self) -> Result<Box<dyn Block>> {
|
|
fn open(self) -> Result<Box<dyn Block>> {
|
|
let stream = BlockStream::new(self.inner, self.creds)?;
|
|
let stream = BlockStream::new(self.inner, self.creds)?;
|
|
let block_key = stream.block_key()?;
|
|
let block_key = stream.block_key()?;
|
|
- let stream = MerkleStream::new(stream)?;
|
|
|
|
|
|
+ let mut stream = MerkleStream::new(stream)?;
|
|
|
|
+ stream.assert_root_integrity()?;
|
|
if self.encrypt {
|
|
if self.encrypt {
|
|
let stream = SecretStream::new(block_key).try_compose(stream)?;
|
|
let stream = SecretStream::new(block_key).try_compose(stream)?;
|
|
let stream = SectoredBuf::new().try_compose(stream)?;
|
|
let stream = SectoredBuf::new().try_compose(stream)?;
|
|
@@ -619,19 +647,15 @@ impl<T> SectoredBuf<T> {
|
|
impl<T: Read + Seek> SectoredBuf<T> {
|
|
impl<T: Read + Seek> SectoredBuf<T> {
|
|
/// Fills the internal buffer by reading from the inner stream at the current position
|
|
/// Fills the internal buffer by reading from the inner stream at the current position
|
|
/// and updates `self.buf_start` with the position read from.
|
|
/// and updates `self.buf_start` with the position read from.
|
|
- fn fill_internal_buf(&mut self) -> io::Result<usize> {
|
|
|
|
|
|
+ fn fill_internal_buf(&mut self) -> Result<usize> {
|
|
self.buf_start = self.inner.stream_position()?.try_into().box_err()?;
|
|
self.buf_start = self.inner.stream_position()?.try_into().box_err()?;
|
|
let read_bytes = if self.buf_start < self.len {
|
|
let read_bytes = if self.buf_start < self.len {
|
|
let read_bytes = self.inner.fill_buf(&mut self.buf)?;
|
|
let read_bytes = self.inner.fill_buf(&mut self.buf)?;
|
|
if read_bytes < self.buf.len() {
|
|
if read_bytes < self.buf.len() {
|
|
- return Err(io::Error::new(
|
|
|
|
- io::ErrorKind::Other,
|
|
|
|
- format!(
|
|
|
|
- "Failed to fill SectoredBuf.buf. Expected {} bytes, got {}.",
|
|
|
|
- self.buf.len(),
|
|
|
|
- read_bytes
|
|
|
|
- ),
|
|
|
|
- ));
|
|
|
|
|
|
+ return Err(Error::IncorrectSize {
|
|
|
|
+ expected: self.buf.len(),
|
|
|
|
+ actual: read_bytes,
|
|
|
|
+ });
|
|
}
|
|
}
|
|
read_bytes
|
|
read_bytes
|
|
} else {
|
|
} else {
|
|
@@ -670,10 +694,15 @@ impl<T: Sectored + Read + Seek> TryCompose<T, SectoredBuf<T>> for SectoredBuf<()
|
|
sectored.buf.resize(sect_sz, 0);
|
|
sectored.buf.resize(sect_sz, 0);
|
|
let len_stored = match sectored.fill_internal_buf() {
|
|
let len_stored = match sectored.fill_internal_buf() {
|
|
Ok(bytes_read) => bytes_read >= Self::RESERVED,
|
|
Ok(bytes_read) => bytes_read >= Self::RESERVED,
|
|
- Err(err) => {
|
|
|
|
- error!("SectoredBuf::fill_internal_buf returned an error: {}", err);
|
|
|
|
|
|
+ Err(Error::IncorrectSize { actual, expected }) => {
|
|
|
|
+ if actual > 0 {
|
|
|
|
+ return Err(Error::IncorrectSize { expected, actual });
|
|
|
|
+ }
|
|
|
|
+ // When the actual size was 0 that just means the inner stream was empty, which is
|
|
|
|
+ // not an error.
|
|
false
|
|
false
|
|
}
|
|
}
|
|
|
|
+ Err(err) => return Err(err),
|
|
};
|
|
};
|
|
if len_stored {
|
|
if len_stored {
|
|
if let Ok(len) = read_from::<u64, _>(&mut sectored.buf.as_slice()) {
|
|
if let Ok(len) = read_from::<u64, _>(&mut sectored.buf.as_slice()) {
|
|
@@ -756,6 +785,7 @@ impl<T: Seek + Read + Write> Write for SectoredBuf<T> {
|
|
write_to(&len, &mut self.buf.as_mut_slice()).box_err()?;
|
|
write_to(&len, &mut self.buf.as_mut_slice()).box_err()?;
|
|
self.inner.seek(SeekFrom::Start(0))?;
|
|
self.inner.seek(SeekFrom::Start(0))?;
|
|
self.inner.write_all(&self.buf)?;
|
|
self.inner.write_all(&self.buf)?;
|
|
|
|
+ self.inner.flush()?;
|
|
|
|
|
|
// Seek to the next position.
|
|
// Seek to the next position.
|
|
self.inner.seek(SeekFrom::Start(seek_to))?;
|
|
self.inner.seek(SeekFrom::Start(seek_to))?;
|
|
@@ -786,7 +816,7 @@ impl<T: Read + Seek> Read for SectoredBuf<T> {
|
|
let byte_ct = match self.fill_internal_buf() {
|
|
let byte_ct = match self.fill_internal_buf() {
|
|
Ok(byte_ct) => byte_ct,
|
|
Ok(byte_ct) => byte_ct,
|
|
Err(err) => {
|
|
Err(err) => {
|
|
- error!("SectoredBuf::full_internal_buf returned an error: {}", err);
|
|
|
|
|
|
+ warn!("SectoredBuf::full_internal_buf returned an error: {}", err);
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
};
|
|
@@ -847,6 +877,10 @@ impl<T: HeaderAccess> HeaderAccess for SectoredBuf<T> {
|
|
fn add_readcap_for(&mut self, owner: Principal, key: &dyn Encrypter) -> Result<()> {
|
|
fn add_readcap_for(&mut self, owner: Principal, key: &dyn Encrypter) -> Result<()> {
|
|
self.inner.add_readcap_for(owner, key)
|
|
self.inner.add_readcap_for(owner, key)
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ fn integrity(&self) -> Option<&[u8]> {
|
|
|
|
+ self.inner.integrity()
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
impl<T: Read + Write + Seek + HeaderAccess> Block for SectoredBuf<T> {}
|
|
impl<T: Read + Write + Seek + HeaderAccess> Block for SectoredBuf<T> {}
|
|
@@ -946,7 +980,7 @@ impl FragmentRecord {
|
|
|
|
|
|
/// An identifier for a security principal, which is any entity that can be authenticated.
|
|
/// 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)]
|
|
-struct Principal(Hash);
|
|
|
|
|
|
+pub struct Principal(Hash);
|
|
|
|
|
|
impl Principal {
|
|
impl Principal {
|
|
fn kind(&self) -> HashKind {
|
|
fn kind(&self) -> HashKind {
|
|
@@ -1153,7 +1187,10 @@ struct FragmentSerial(u32);
|
|
mod tests {
|
|
mod tests {
|
|
use std::io::Cursor;
|
|
use std::io::Cursor;
|
|
|
|
|
|
|
|
+ use crate::crypto::{tpm::TpmCredStore, CredStore};
|
|
|
|
+
|
|
use super::*;
|
|
use super::*;
|
|
|
|
+ use tempdir::TempDir;
|
|
use test_helpers::*;
|
|
use test_helpers::*;
|
|
|
|
|
|
fn path_from_str_test_case(
|
|
fn path_from_str_test_case(
|
|
@@ -1603,4 +1640,60 @@ mod tests {
|
|
|
|
|
|
assert_eq!(EXPECTED, actual);
|
|
assert_eq!(EXPECTED, actual);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ #[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(Cursor::new(Vec::<u8>::new()))
|
|
|
|
+ .with_creds(creds)
|
|
|
|
+ .with_encrypt(true)
|
|
|
|
+ .open()
|
|
|
|
+ .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 creds = cred_store.node_creds().expect("failed to get node creds");
|
|
|
|
+ {
|
|
|
|
+ let file = OpenOptions::new()
|
|
|
|
+ .create_new(true)
|
|
|
|
+ .write(true)
|
|
|
|
+ .read(true)
|
|
|
|
+ .open(&file_path)
|
|
|
|
+ .expect("failed to open file");
|
|
|
|
+ let mut block = BlockOpenOptions::new()
|
|
|
|
+ .with_inner(file)
|
|
|
|
+ .with_creds(creds.clone())
|
|
|
|
+ .with_encrypt(true)
|
|
|
|
+ .open()
|
|
|
|
+ .expect("failed to open block");
|
|
|
|
+ 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(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);
|
|
|
|
+ }
|
|
}
|
|
}
|