Răsfoiți Sursa

* Introduced a NewBlock struct which will replace Block.
* Implemented SecretStream.

Matthew Carr 2 ani în urmă
părinte
comite
970315cc31
3 a modificat fișierele cu 497 adăugiri și 26 ștergeri
  1. 337 10
      crates/btnode/src/crypto/mod.rs
  2. 4 2
      crates/btnode/src/crypto/tpm.rs
  3. 156 14
      crates/btnode/src/main.rs

+ 337 - 10
crates/btnode/src/crypto/mod.rs

@@ -2,7 +2,30 @@
 
 mod tpm;
 
-use super::*;
+use super::{
+    Block,
+    Writecap,
+    Serialize,
+    Deserialize,
+    Display,
+    Formatter,
+    fmt,
+    Hashable,
+    BigArray,
+    Owned,
+    Principal,
+    Path,
+    HashMap,
+    Epoch,
+    io,
+    Read,
+    Write,
+    Seek,
+    Header,
+    Compose,
+    Sectored,
+    SECTOR_SZ_DEFAULT,
+};
 
 use openssl::{
     error::ErrorStack,
@@ -21,7 +44,7 @@ use serde::{
 };
 use zeroize::ZeroizeOnDrop;
 use std::{
-    str::FromStr, num::TryFromIntError, marker::PhantomData,
+    str::FromStr, num::TryFromIntError, marker::PhantomData, io::{ErrorKind, SeekFrom},
 };
 use strum_macros::{EnumString, EnumDiscriminants, Display};
 use foreign_types::ForeignType;
@@ -45,15 +68,16 @@ pub enum Error {
     BlockNotEncrypted,
     InvalidHashFormat,
     InvalidSignature,
-    IncorrectSize(usize),
+    IncorrectSize { expected: usize, actual: usize },
+    IndivisibleSize { divisor: usize, actual: usize },
     WritecapAuthzErr(WritecapAuthzErr),
     Serde(serde_block_tree::Error),
     Io(std::io::Error),
-    Custom(Box<dyn std::fmt::Debug>)
+    Custom(Box<dyn std::fmt::Debug + Send + Sync>)
 }
 
 impl Error {
-    fn custom<E: std::fmt::Debug + 'static>(err: E) -> Self {
+    fn custom<E: std::fmt::Debug + Send + Sync + 'static>(err: E) -> Self {
         Error::Custom(Box::new(err))
     }
 }
@@ -68,7 +92,13 @@ impl Display for Error {
             Error::BlockNotEncrypted => write!(f, "block was not encrypted"),
             Error::InvalidHashFormat => write!(f, "invalid format"),
             Error::InvalidSignature => write!(f, "invalid signature"),
-            Error::IncorrectSize(size) => write!(f, "incorrect size: {}", size),
+            Error::IncorrectSize { expected, actual }
+                => write!(f, "expected size {} but got {}", expected, actual),
+            Error::IndivisibleSize { divisor, actual }
+                => write!(
+                    f,
+                    "expected a size which is divisible by {} but got {}", divisor, actual
+                ),
             Error::WritecapAuthzErr(err) => err.fmt(f),
             Error::Serde(err) => err.fmt(f),
             Error::Io(err) => err.fmt(f),
@@ -77,6 +107,8 @@ impl Display for Error {
     }
 }
 
+impl std::error::Error for Error {}
+
 impl From<WritecapAuthzErr> for Error {
     fn from(err: WritecapAuthzErr) -> Self {
         Error::WritecapAuthzErr(err)
@@ -107,6 +139,12 @@ impl From<TryFromIntError> for Error {
     }
 }
 
+impl From<Error> for io::Error {
+    fn from(err: Error) -> Self {
+        io::Error::new(io::ErrorKind::Other, err)
+    }
+}
+
 pub(crate) type Result<T> = std::result::Result<T, Error>;
 
 /// Returns an array of the given length filled with cryptographically strong random data.
@@ -293,7 +331,7 @@ impl AeadKeyKind {
 fn array_from<const N: usize>(slice: &[u8]) -> Result<[u8; N]> {
     let slice_len = slice.len();
     if N != slice_len {
-        return Err(Error::IncorrectSize(slice_len))
+        return Err(Error::IncorrectSize { actual: slice_len, expected: N })
     }
     let mut array = [0u8; N];
     array.copy_from_slice(slice);
@@ -409,6 +447,29 @@ impl SymKey {
         };
         SymParams { cipher, key, iv }
     }
+
+    fn block_size(&self) -> usize {
+        let SymParams { cipher, .. } = self.params();
+        cipher.block_size()
+    }
+
+    // The number of bytes that the plaintext expands by when encrypted.
+    fn expansion_sz(&self) -> usize {
+        match self {
+            SymKey::Aes256Cbc { .. } => 16,
+            SymKey::Aes256Ctr { .. } => 0,
+        }
+    }
+
+    fn to_encrypter(&self) -> Result<Crypter> {
+        let SymParams { cipher, key, iv } = self.params();
+        Ok(Crypter::new(cipher, Mode::Encrypt, key, iv)?)
+    }
+
+    fn to_decrypter(&self) -> Result<Crypter> {
+        let SymParams { cipher, key, iv } = self.params();
+        Ok(Crypter::new(cipher, Mode::Decrypt, key, iv)?)
+    }
 }
 
 impl Encrypter for SymKey {
@@ -1038,6 +1099,193 @@ pub(crate) trait CredStore {
     ) -> Result<Self::CredHandle>;
 }
 
+struct MerkleStream<T> {
+    inner: T,
+    pos: usize,
+    sector_len: usize,
+    //tree: ???,
+}
+
+impl MerkleStream<()> {
+    fn new() -> Self {
+        MerkleStream {
+            inner: (),
+            pos: 0,
+            sector_len: SECTOR_SZ_DEFAULT,
+        }
+    }
+}
+
+impl<T> Compose<T, MerkleStream<T>> for MerkleStream<()> {
+    fn compose(self, inner: T) -> MerkleStream<T> {
+        MerkleStream {
+            inner,
+            pos: 0,
+            sector_len: self.sector_len,
+        }
+    }
+}
+
+impl<T: Write> Write for MerkleStream<T> {
+    fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
+        unimplemented!()
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        unimplemented!()
+    }
+}
+
+impl<T: Read> Read for MerkleStream<T> {
+    fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
+        unimplemented!()
+    }
+}
+
+impl<T: Seek> Seek for MerkleStream<T> {
+    fn seek(&mut self, _pos: io::SeekFrom) -> io::Result<u64> {
+        unimplemented!()
+    }
+}
+
+// A stream which encrypts all data written to it and decrypts all data read from it.
+struct SecretStream<T> {
+    inner: T,
+    key: SymKey,
+    /// Buffer for ciphertext.
+    ct_buf: Vec<u8>,
+    /// Buffer for plaintext.
+    pt_buf: Vec<u8>,
+    // The sector size of this stream. Reads and writes are only accepted for buffers of this size.
+    sect_sz: usize,
+    // The sector size of the inner stream. Reads and writes are only executed using buffers of
+    // this size.
+    inner_sect_sz: usize,
+}
+
+impl<T> SecretStream<T> {
+    fn new(key: SymKey, inner: T, inner_sect_sz: usize) -> Result<SecretStream<T>> {
+        let expansion_sz = key.expansion_sz();
+        let sect_sz = inner_sect_sz - expansion_sz;
+        let block_sz = key.block_size();
+        if 0 != sect_sz % block_sz {
+            return Err(Error::IndivisibleSize { divisor: block_sz, actual: sect_sz })
+        }
+        let ct_buf = vec![0u8; inner_sect_sz];
+        let pt_buf = vec![0u8; inner_sect_sz + block_sz];
+        Ok(SecretStream { inner, key, ct_buf, pt_buf, sect_sz, inner_sect_sz, })
+    }
+
+    /// Given an offset into this stream, produces the corresponding offset into the inner stream.
+    fn inner_offset(&self, outer_offset: u64) -> u64{
+        let sect_sz = self.sect_sz as u64;
+        let inner_sect_sz = self.inner_sect_sz as u64;
+        // We return the offset into the current sector, plus the size of all previous sectors.
+        outer_offset % sect_sz + outer_offset / sect_sz * inner_sect_sz 
+    }
+
+    /// Given an offset into the inner stream, returns the corresponding offset into this stream.
+    fn outer_offset(&self, inner_offset: u64) -> u64{
+        let sect_sz = self.sect_sz as u64;
+        let inner_sect_sz = self.inner_sect_sz as u64;
+        inner_offset % inner_sect_sz + inner_offset / inner_sect_sz * sect_sz
+    }
+}
+
+impl<T, U: Sectored> Compose<U, SecretStream<U>> for SecretStream<T> {
+    fn compose(self, inner: U) -> SecretStream<U> {
+        let inner_sect_sz = inner.sector_sz();
+        SecretStream {
+            sect_sz: inner_sect_sz - self.key.expansion_sz(),
+            inner_sect_sz,
+            inner,
+            key: self.key,
+            ct_buf: self.ct_buf,
+            pt_buf: self.pt_buf,
+        }
+    }
+}
+
+impl<T> Sectored for SecretStream<T> {
+    fn sector_sz(&self) -> usize {
+        self.sect_sz
+    }
+}
+
+impl<T: Write> Write for SecretStream<T> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        if buf.len() != self.sect_sz {
+            return Err(io::Error::new(
+                ErrorKind::Other,
+                Error::IncorrectSize { actual: buf.len(), expected: self.sect_sz, }
+            ));
+        }
+
+        self.ct_buf.resize(self.inner_sect_sz, 0);
+        let mut encrypter = self.key.to_encrypter()?;
+        let mut count = encrypter.update(buf, &mut self.ct_buf)?;
+        count += encrypter.finalize(&mut self.ct_buf[count..])?;
+        self.ct_buf.truncate(count);
+
+        self.inner.write_all(&self.ct_buf).map(|_| buf.len())
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.inner.flush()
+    }
+}
+
+impl<T: Read> Read for SecretStream<T> {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        if buf.len() != self.sect_sz {
+            return Err(io::Error::new(
+                ErrorKind::Other,
+                Error::IncorrectSize { actual: buf.len(), expected: self.sect_sz, }
+            ));
+        }
+
+        self.ct_buf.resize(self.inner_sect_sz, 0);
+        self.inner.read_exact(&mut self.ct_buf)?;
+
+        self.pt_buf.resize(self.inner_sect_sz + self.key.block_size(), 0);
+        let mut decrypter = self.key.to_decrypter()?;
+        let mut count = decrypter.update(&self.ct_buf, &mut self.pt_buf)?;
+        count += decrypter.finalize(&mut self.pt_buf[count..])?;
+        self.pt_buf.truncate(count);
+
+        buf.copy_from_slice(&self.pt_buf);
+        Ok(buf.len())
+    }
+}
+
+impl<T: Seek> Seek for SecretStream<T> {
+    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
+        let outer_offset = match pos {
+            SeekFrom::Start(offset) => offset,
+            SeekFrom::Current(offset) => {
+                let inner_offset = self.inner.stream_position()?;
+                let outer_offset = self.outer_offset(inner_offset);
+                if offset >= 0 {
+                    outer_offset + offset as u64
+                }
+                else {
+                    outer_offset - (-offset) as u64
+                }
+            }
+            SeekFrom::End(_) => {
+                // We can support this once stream_len is stabilized:
+                // https://github.com/rust-lang/rust/issues/59359
+                return Err(io::Error::new(
+                    ErrorKind::Unsupported,
+                    "seeking from the end of the stream is not supported"))
+            },
+        };
+        let inner_offset = self.inner_offset(outer_offset);
+        self.inner.seek(SeekFrom::Start(inner_offset))?;
+        Ok(outer_offset)
+    }
+}
+
 pub(crate) fn encrypt_block<C: Encrypter + Decrypter>(
     mut block: Block, principal: &Principal, creds: &C
 ) -> Result<Block> {
@@ -1221,10 +1469,17 @@ pub(crate) fn verify_writecap(mut writecap: &Writecap, path: &Path) -> Result<()
     Err(WritecapAuthzErr::ChainTooLong(CHAIN_LEN_LIMIT).into())
 }
 
+pub fn verify_header(_header: &Header, _sig: &Signature) -> Result<()> {
+    unimplemented!()
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
-    use test_helpers::*;
+    use super::super::test_helpers::*;
+    use std::{
+        time::Duration, io::{Cursor, SeekFrom},
+    };
 
     fn encrypt_decrypt_block_test_case<C: Creds>(
         mut actual: Block, principal: &Principal, creds: &C
@@ -1373,7 +1628,7 @@ mod tests {
     }
 
     #[test]
-    fn encrypt_decrypt_aes256gcm() {
+    fn aeadkey_encrypt_decrypt_aes256gcm() {
         let key = AeadKey::new(AeadKeyKind::AesGcm256).expect("failed to create key");
         let aad = [0u8; 16];
         let expected = [0u8; 32];
@@ -1383,7 +1638,7 @@ mod tests {
     }
 
     #[test]
-    fn decrypt_fails_when_ct_modified() {
+    fn aeadkey_decrypt_fails_when_ct_modified() {
         let key = AeadKey::new(AeadKeyKind::AesGcm256).expect("failed to create key");
         let aad = [0u8; 16];
         let expected = [0u8; 32];
@@ -1399,6 +1654,78 @@ mod tests {
         assert!(result.is_err())
     }
 
+    fn secret_stream(key_kind: SymKeyKind, num_sectors: usize) -> SecretStream<Cursor<Vec<u8>>> {
+        let key = SymKey::generate(key_kind).expect("key generation failed");
+        let inner = Cursor::new(vec![0u8; num_sectors * SECTOR_SZ_DEFAULT]);
+        SecretStream::new(key, inner, SECTOR_SZ_DEFAULT)
+            .expect("secret_stream creation failed")
+    }
+
+    fn secret_stream_encrypt_decrypt_are_inverse_test_case(key_kind: SymKeyKind) {
+        let mut stream = secret_stream(key_kind, 3);
+        let sector_sz = stream.sector_sz();
+        let expected = vec![1u8; 3 * sector_sz];
+
+        for sector in expected.chunks(sector_sz) {
+            stream.write(sector).expect("write failed");
+        }
+        stream.seek(SeekFrom::Start(0)).expect("seek failed");
+        let mut actual = vec![0u8; 3 * sector_sz];
+        for sector in actual.chunks_mut(sector_sz) {
+            stream.read(sector).expect("read failed");
+        }
+
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn secret_stream_encrypt_decrypt_are_inverse_aes256cbc() {
+        secret_stream_encrypt_decrypt_are_inverse_test_case(SymKeyKind::Aes256Cbc)
+    }
+
+    #[test]
+    fn secret_stream_encrypt_decrypt_are_inverse_aes256ctr() {
+        secret_stream_encrypt_decrypt_are_inverse_test_case(SymKeyKind::Aes256Ctr)
+    }
+
+    #[test]
+    fn secret_stream_seek_from_start() {
+        let mut stream = secret_stream(SymKeyKind::Aes256Cbc, 3);
+        let sector_sz = stream.sector_sz();
+        let expected = vec![2u8; sector_sz];
+        // Write one sector of ones, one sector of twos and one sector of threes.
+        for k in 1..4 {
+            let sector: Vec<u8> = std::iter::repeat(k as u8).take(sector_sz).collect();
+            stream.write(&sector).expect("writing to stream failed");
+        }
+
+        stream.seek(SeekFrom::Start(sector_sz as u64)).expect("seek failed");
+
+        // A read from the stream should now return the second sector, which is filled with twos.
+        let mut actual = vec![0u8; sector_sz];
+        stream.read(&mut actual).expect("reading from stream failed");
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn secret_stream_seek_from_current() {
+        let mut stream = secret_stream(SymKeyKind::Aes256Cbc, 3);
+        let sector_sz = stream.sector_sz();
+        let expected = vec![3u8; sector_sz];
+        // Write one sector of ones, one sector of twos and one sector of threes.
+        for k in 1..4 {
+            let sector: Vec<u8> = std::iter::repeat(k as u8).take(sector_sz).collect();
+            stream.write(&sector).expect("writing to stream failed");
+        }
+
+        stream.seek(SeekFrom::Current(-1 * (sector_sz as i64))).expect("seek failed");
+
+        // A read from the stream should now return the last sector, which is filled with threes.
+        let mut actual = vec![0u8; sector_sz];
+        stream.read(&mut actual).expect("reading from stream failed");
+        assert_eq!(expected, actual);
+    }
+
     /// Tests that validate the dependencies of this module.
     mod dependency_tests {
         use super::*;

+ 4 - 2
crates/btnode/src/crypto/tpm.rs

@@ -1,7 +1,7 @@
 use super::*;
 
 use std::{
-    io::{Read, Write, BufReader},
+    io::{Read, Write, BufReader, BufWriter},
     os::{
         raw::c_char,
         unix::fs::PermissionsExt,
@@ -17,6 +17,7 @@ use openssl::{
     hash::Hasher,
     nid::Nid, pkcs5::pbkdf2_hmac,
 };
+use serde_block_tree::read_from;
 use tss_esapi_sys::{
     TSS2_RC,
     TPMT_TK_HASHCHECK,
@@ -70,6 +71,7 @@ use tss_esapi::{
 };
 use serde::ser;
 use zeroize::Zeroizing;
+use log::error;
 
 impl From<tss_esapi::Error> for Error {
     fn from(err: tss_esapi::Error) -> Self {
@@ -1216,7 +1218,7 @@ impl<S: Scheme> AsymKeyPub<S> {
 impl AsymKeyPub<Encrypt> {
     fn storage_key_public(&self) -> Result<Public> {
         fn from_rsa(
-            scheme: Encrypt, rsa: openssl::rsa::Rsa<crypto::Public>
+            scheme: Encrypt, rsa: openssl::rsa::Rsa<super::Public>
         ) -> Result<Public> {
             let exponent = rsa.e().try_into_u32()?;
             let modulus = rsa.n().to_vec();

+ 156 - 14
crates/btnode/src/main.rs

@@ -23,12 +23,38 @@ use std::{
     fmt::{self, Display, Formatter},
     time::{Duration, SystemTime},
     ops::{Add, Sub},
-    io::{self, BufWriter, Write},
+    io::{self, BufWriter, Write, Read, Seek, SeekFrom}, fs::{File, OpenOptions},
 };
 use serde::{Serialize, Deserialize};
 use serde_big_array::BigArray;
 use log::{info, error};
 
+enum Error {
+    Io(std::io::Error),
+    Serde(serde_block_tree::Error),
+    Crypto(crypto::Error),
+}
+
+impl From<std::io::Error> for Error {
+    fn from(err: std::io::Error) -> Self {
+        Error::Io(err)
+    }
+}
+
+impl From<serde_block_tree::Error> for Error {
+    fn from(err: serde_block_tree::Error) -> Self {
+        Error::Serde(err)
+    }
+}
+
+impl From<crypto::Error> for Error {
+    fn from(err: crypto::Error) -> Self {
+        Error::Crypto(err)
+    }
+}
+
+type Result<T> = std::result::Result<T, Error>;
+
 /// A Block tagged with its version number. When a block of a previous version is received over
 /// the network or read from the filesystem, it is upgraded to the current version before being
 /// processed.
@@ -37,6 +63,122 @@ enum VersionedBlock {
     V0(Block)
 }
 
+const SECTOR_SZ_DEFAULT: usize = 4096;
+
+// A trait for streams which only allow reads and writes in fixed sized units called sectors.
+trait Sectored {
+    // Returns the size of the sector for this stream.
+    fn sector_sz(&self) -> usize;
+}
+
+#[derive(Serialize, Deserialize, Debug, PartialEq)]
+pub struct Header {
+    path: Path,
+    readcaps: HashMap<Principal, Cryptotext<SymKey>>,
+    writecap: Writecap,
+    merkle_root: Hash,
+}
+
+struct NewBlock<T> {
+    header: Header,
+    sig: Signature,
+    body: T,
+}
+
+impl<T> NewBlock<T> {
+    fn compose<U, V: Compose<T, U>>(self, new_body: V) -> NewBlock<U> {
+        NewBlock {
+            header: self.header,
+            sig: self.sig,
+            body: new_body.compose(self.body),
+        }
+    }
+}
+
+impl NewBlock<File> {
+    fn new<P: AsRef<std::path::Path>>(path: P) -> Result<NewBlock<FileBody>> {
+        let mut file = OpenOptions::new().read(true).write(true).open(path)?;
+        let header: Header = read_from(&mut file)?;
+        let sig: Signature = read_from(&mut file)?;
+        crypto::verify_header(&header, &sig)?;
+        Ok(NewBlock {
+            header,
+            sig,
+            body: FileBody::new(file)?,
+        })
+    }
+}
+
+impl<T: Write> Write for NewBlock<T> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.body.write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.body.flush()
+    }
+}
+
+impl<T: Read> Read for NewBlock<T> {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        self.body.read(buf)
+    }
+}
+
+impl<T: Seek> Seek for NewBlock<T> {
+    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
+        self.body.seek(pos)
+    }
+}
+
+trait Compose<T, U> {
+    fn compose(self, inner: T) -> U;
+}
+
+struct FileBody {
+    body_offset: u64,
+    file: File,
+}
+
+impl FileBody {
+    /// Create a new `FileBody` which wraps the given `File`. The current position of the file
+    /// is used as the offset into the file where the body starts.
+    fn new(mut file: File) -> Result<FileBody> {
+        Ok(FileBody {
+            body_offset: file.stream_position()?,
+            file,
+        })
+    }
+}
+
+impl Write for FileBody {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.file.write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.file.flush()
+    }
+}
+
+impl Read for FileBody {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        self.file.read(buf)
+    }
+}
+
+impl Seek for FileBody {
+    /// Seeks the file to the given position in the body. The offset given to this method is the
+    /// offset into the body, thus to get the offset into the file we add the offset of the body.
+    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
+        match pos {
+            SeekFrom::Start(pos) => self.file.seek(SeekFrom::Start(pos + self.body_offset)),
+            SeekFrom::Current(pos) => self.file.seek(SeekFrom::Current(pos)),
+            SeekFrom::End(_pos) => unimplemented!(),
+        }
+    }
+}
+
 /// A container which binds together ciphertext along with the metadata needed to identify,
 /// verify and decrypt it.
 #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
@@ -103,7 +245,7 @@ struct Fragment {
 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`.
-    fn new(path_str: &str, serial_num: u32, body: Vec<u8>) -> Result<Fragment, PathError> {
+    fn new(path_str: &str, serial_num: u32, body: Vec<u8>) -> std::result::Result<Fragment, PathError> {
         let result = Path::try_from(path_str);
         Ok(Fragment {
             path: result?,
@@ -180,7 +322,7 @@ impl Path {
     /// current path component.
     fn component_end<I: Iterator<Item = (usize, char)>>(
         start: usize, first: char, pairs: &mut I
-    ) -> Result<usize, PathError> {
+    ) -> std::result::Result<usize, PathError> {
         if first == Path::SEP {
             return Err(PathError::EmptyComponent);
         }
@@ -208,7 +350,7 @@ impl Path {
     }
 
     /// Asserts that the number of bytes in the given string is no more than `Path::BYTE_LIMIT`.
-    fn assert_not_too_long(string: &str) -> Result<(), PathError> {
+    fn assert_not_too_long(string: &str) -> std::result::Result<(), PathError> {
         let len = string.len();
         if len > Path::BYTE_LIMIT {
             return Err(PathError::PathTooLong(len))
@@ -240,7 +382,7 @@ impl Path {
 impl<'s> TryFrom<&'s str> for Path {
     type Error = PathError;
 
-    fn try_from(string: &'s str) -> Result<Path, PathError> {
+    fn try_from(string: &'s str) -> std::result::Result<Path, PathError> {
         Path::assert_not_too_long(string)?;
         let mut pairs = string.char_indices();
         let mut components = Vec::new();
@@ -389,15 +531,15 @@ mod tests {
     use test_helpers::*;
 
     fn path_from_str_test_case(
-        expected: Result<Path, PathError>, input: &str
-    ) -> Result<(), PathError> {
+        expected: std::result::Result<Path, PathError>, input: &str
+    ) -> std::result::Result<(), PathError> {
         let result = Path::try_from(input);
         assert_eq!(expected, result);
         Ok(())
     }
 
     #[test]
-    fn path_from_str_multiple_components_ok() -> Result<(), PathError> {
+    fn path_from_str_multiple_components_ok() -> std::result::Result<(), PathError> {
         let expected = make_path(vec!["red", "green", "blue"]);
         let input = format!("{}/red/green/blue", expected.owner.0);
         path_from_str_test_case(Ok(expected), input.as_str())?;
@@ -405,7 +547,7 @@ mod tests {
     }
 
     #[test]
-    fn path_from_str_one_component_ok() -> Result<(), PathError> {
+    fn path_from_str_one_component_ok() -> std::result::Result<(), PathError> {
         let expected = make_path(vec![]);
         let input = expected.owner.0.to_string();
         path_from_str_test_case(Ok(expected), input.as_str())?;
@@ -413,7 +555,7 @@ mod tests {
     }
 
     #[test]
-    fn path_from_str_trailing_slash_ok() -> Result<(), PathError> {
+    fn path_from_str_trailing_slash_ok() -> std::result::Result<(), PathError> {
         // Notice the empty component at the end of this path due to the trailing slash.
         let expected = make_path(vec!["orange", "banana", "shotgun", ""]);
         let input = format!("{}/orange/banana/shotgun/", expected.owner.0);
@@ -422,7 +564,7 @@ mod tests {
     }
 
     #[test]
-    fn path_from_str_path_too_long_fail() -> Result<(), PathError> {
+    fn path_from_str_path_too_long_fail() -> std::result::Result<(), PathError> {
         let principal = make_principal();
         let input = format!("{}/{}", principal.0, "*".repeat(4097));
         let expected = Err(PathError::PathTooLong(input.len()));
@@ -431,7 +573,7 @@ mod tests {
     }
 
     #[test]
-    fn path_from_str_multiple_slashes_fail() -> Result<(), PathError> {
+    fn path_from_str_multiple_slashes_fail() -> std::result::Result<(), PathError> {
         let expected = Err(PathError::EmptyComponent);
         let input = format!("{}//orange", make_principal().0);
         path_from_str_test_case(expected, input.as_str())?;
@@ -439,7 +581,7 @@ mod tests {
     }
 
     #[test]
-    fn path_from_str_leading_slash_fail() -> Result<(), PathError> {
+    fn path_from_str_leading_slash_fail() -> std::result::Result<(), PathError> {
         let expected = Err(PathError::EmptyComponent);
         let input = format!("/{}/orange/banana/shotgun", make_principal().0);
         path_from_str_test_case(expected, input.as_str())?;
@@ -447,7 +589,7 @@ mod tests {
     }
 
     #[test]
-    fn path_round_trip() -> Result<(), PathError> {
+    fn path_round_trip() -> std::result::Result<(), PathError> {
         let expected = make_path(vec!["interstitial", "inter-related", "intersections"]);
         let actual = Path::try_from(expected.to_string().as_str())?;
         assert_eq!(expected, actual);