|  | @@ -19,11 +19,11 @@ use brotli::{CompressorWriter, Decompressor};
 | 
	
		
			
				|  |  |  use btserde::{self, read_from, write_to};
 | 
	
		
			
				|  |  |  mod 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_big_array::BigArray;
 | 
	
		
			
				|  |  |  use std::{
 | 
	
	
		
			
				|  | @@ -40,7 +40,7 @@ use std::{
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #[derive(Debug)]
 | 
	
		
			
				|  |  | -enum Error {
 | 
	
		
			
				|  |  | +pub enum Error {
 | 
	
		
			
				|  |  |      MissingWritecap,
 | 
	
		
			
				|  |  |      Io(std::io::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 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.
 | 
	
	
		
			
				|  | @@ -221,7 +223,7 @@ pub struct Header {
 | 
	
		
			
				|  |  |      readcaps: HashMap<Principal, Ciphertext<SymKey>>,
 | 
	
		
			
				|  |  |      writecap: Option<Writecap>,
 | 
	
		
			
				|  |  |      /// A hash which provides integrity for the contents of the block body.
 | 
	
		
			
				|  |  | -    integrity: Hash,
 | 
	
		
			
				|  |  | +    integrity: Option<Hash>,
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #[derive(Serialize, Deserialize, Default)]
 | 
	
	
		
			
				|  | @@ -237,19 +239,35 @@ struct BlockStream<T, 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>> {
 | 
	
		
			
				|  |  | -        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 {
 | 
	
		
			
				|  |  |              trailered,
 | 
	
		
			
				|  |  | -            trailer: trailer.unwrap_or_default(),
 | 
	
		
			
				|  |  | +            trailer,
 | 
	
		
			
				|  |  |              header_buf: Vec::new(),
 | 
	
		
			
				|  |  |              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>> {
 | 
	
		
			
				|  |  |          let inner = OpenOptions::new().read(true).write(true).open(path)?;
 | 
	
		
			
				|  |  |          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> {
 | 
	
		
			
				|  |  |      fn flush_integ(&mut self, integrity: &[u8]) -> io::Result<()> {
 | 
	
		
			
				|  |  |          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();
 | 
	
		
			
				|  |  |          write_to(&header, &mut self.header_buf).box_err()?;
 | 
	
		
			
				|  |  |          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);
 | 
	
		
			
				|  |  |          Ok(())
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn integrity(&self) -> Option<&[u8]> {
 | 
	
		
			
				|  |  | +        self.trailer
 | 
	
		
			
				|  |  | +            .header
 | 
	
		
			
				|  |  | +            .integrity
 | 
	
		
			
				|  |  | +            .as_ref()
 | 
	
		
			
				|  |  | +            .map(|hash| hash.as_ref())
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  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>> {
 | 
	
		
			
				|  |  |          let stream = BlockStream::new(self.inner, self.creds)?;
 | 
	
		
			
				|  |  |          let block_key = stream.block_key()?;
 | 
	
		
			
				|  |  | -        let stream = MerkleStream::new(stream)?;
 | 
	
		
			
				|  |  | +        let mut stream = MerkleStream::new(stream)?;
 | 
	
		
			
				|  |  | +        stream.assert_root_integrity()?;
 | 
	
		
			
				|  |  |          if self.encrypt {
 | 
	
		
			
				|  |  |              let stream = SecretStream::new(block_key).try_compose(stream)?;
 | 
	
		
			
				|  |  |              let stream = SectoredBuf::new().try_compose(stream)?;
 | 
	
	
		
			
				|  | @@ -619,19 +647,15 @@ impl<T> SectoredBuf<T> {
 | 
	
		
			
				|  |  |  impl<T: Read + Seek> SectoredBuf<T> {
 | 
	
		
			
				|  |  |      /// Fills the internal buffer by reading from the inner stream at the current position
 | 
	
		
			
				|  |  |      /// 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()?;
 | 
	
		
			
				|  |  |          let read_bytes = if self.buf_start < self.len {
 | 
	
		
			
				|  |  |              let read_bytes = self.inner.fill_buf(&mut self.buf)?;
 | 
	
		
			
				|  |  |              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
 | 
	
		
			
				|  |  |          } else {
 | 
	
	
		
			
				|  | @@ -670,10 +694,15 @@ impl<T: Sectored + Read + Seek> TryCompose<T, SectoredBuf<T>> for SectoredBuf<()
 | 
	
		
			
				|  |  |          sectored.buf.resize(sect_sz, 0);
 | 
	
		
			
				|  |  |          let len_stored = match sectored.fill_internal_buf() {
 | 
	
		
			
				|  |  |              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
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +            Err(err) => return Err(err),
 | 
	
		
			
				|  |  |          };
 | 
	
		
			
				|  |  |          if len_stored {
 | 
	
		
			
				|  |  |              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()?;
 | 
	
		
			
				|  |  |          self.inner.seek(SeekFrom::Start(0))?;
 | 
	
		
			
				|  |  |          self.inner.write_all(&self.buf)?;
 | 
	
		
			
				|  |  | +        self.inner.flush()?;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          // Seek to the next position.
 | 
	
		
			
				|  |  |          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() {
 | 
	
		
			
				|  |  |                      Ok(byte_ct) => byte_ct,
 | 
	
		
			
				|  |  |                      Err(err) => {
 | 
	
		
			
				|  |  | -                        error!("SectoredBuf::full_internal_buf returned an error: {}", err);
 | 
	
		
			
				|  |  | +                        warn!("SectoredBuf::full_internal_buf returned an error: {}", err);
 | 
	
		
			
				|  |  |                          break;
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                  };
 | 
	
	
		
			
				|  | @@ -847,6 +877,10 @@ impl<T: HeaderAccess> HeaderAccess for SectoredBuf<T> {
 | 
	
		
			
				|  |  |      fn add_readcap_for(&mut self, owner: Principal, key: &dyn Encrypter) -> Result<()> {
 | 
	
		
			
				|  |  |          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> {}
 | 
	
	
		
			
				|  | @@ -946,7 +980,7 @@ 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)]
 | 
	
		
			
				|  |  | -struct Principal(Hash);
 | 
	
		
			
				|  |  | +pub struct Principal(Hash);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  impl Principal {
 | 
	
		
			
				|  |  |      fn kind(&self) -> HashKind {
 | 
	
	
		
			
				|  | @@ -1153,7 +1187,10 @@ struct FragmentSerial(u32);
 | 
	
		
			
				|  |  |  mod tests {
 | 
	
		
			
				|  |  |      use std::io::Cursor;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    use crate::crypto::{tpm::TpmCredStore, CredStore};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      use super::*;
 | 
	
		
			
				|  |  | +    use tempdir::TempDir;
 | 
	
		
			
				|  |  |      use test_helpers::*;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      fn path_from_str_test_case(
 | 
	
	
		
			
				|  | @@ -1603,4 +1640,60 @@ mod tests {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          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);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 |