Преглед изворни кода

Started using quinn for transporting messages.

Matthew Carr пре 2 година
родитељ
комит
041814acab

+ 364 - 6
Cargo.lock

@@ -127,6 +127,22 @@ dependencies = [
  "base64",
 ]
 
+[[package]]
+name = "base64ct"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf"
+
+[[package]]
+name = "bcder"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69dfb7dc0d4aee3f8c723c43553b55662badf692b541ff8e4426df75dae8da9a"
+dependencies = [
+ "bytes",
+ "smallvec",
+]
+
 [[package]]
 name = "bindgen"
 version = "0.59.2"
@@ -213,8 +229,10 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "base64-url",
+ "bcder",
  "brotli",
  "btserde",
+ "bytes",
  "chrono",
  "criterion",
  "ctor",
@@ -237,6 +255,8 @@ dependencies = [
  "tss-esapi",
  "tss-esapi-sys",
  "vm-memory",
+ "webpki",
+ "x509-certificate",
  "zeroize",
 ]
 
@@ -244,14 +264,20 @@ dependencies = [
 name = "btmsg"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "btlib",
  "btserde",
  "bytes",
+ "ctor",
+ "env_logger",
  "futures",
  "lazy_static",
+ "log",
+ "quinn",
+ "rustls",
  "serde",
- "tempdir",
  "tokio",
+ "tokio-stream",
  "tokio-util",
  "zerocopy",
 ]
@@ -429,6 +455,22 @@ dependencies = [
  "unicode-width",
 ]
 
+[[package]]
+name = "const-oid"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.3"
@@ -579,6 +621,15 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "der"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
+dependencies = [
+ "const-oid",
+]
+
 [[package]]
 name = "either"
 version = "1.8.0"
@@ -752,6 +803,17 @@ dependencies = [
  "slab",
 ]
 
+[[package]]
+name = "getrandom"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
 [[package]]
 name = "gimli"
 version = "0.26.2"
@@ -802,6 +864,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
 [[package]]
 name = "hostname-validator"
 version = "1.1.1"
@@ -1131,6 +1199,12 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
 [[package]]
 name = "openssl-src"
 version = "111.22.0+1.1.1q"
@@ -1166,6 +1240,15 @@ version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
 
+[[package]]
+name = "pem"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4"
+dependencies = [
+ "base64",
+]
+
 [[package]]
 name = "pest"
 version = "2.3.1"
@@ -1268,6 +1351,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.43"
@@ -1277,6 +1366,56 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "quinn"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445cbfe2382fa023c4f2f3c7e1c95c03dcc1df2bf23cebcb2b13e1402c4394d1"
+dependencies = [
+ "bytes",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "webpki",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72ef4ced82a24bb281af338b9e8f94429b6eca01b4e66d899f40031f074e74c9"
+dependencies = [
+ "bytes",
+ "rand 0.8.5",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-native-certs",
+ "slab",
+ "thiserror",
+ "tinyvec",
+ "tracing",
+ "webpki",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "641538578b21f5e5c8ea733b736895576d0fe329bb883b937db6f4d163dbaaf4"
+dependencies = [
+ "libc",
+ "quinn-proto",
+ "socket2",
+ "tracing",
+ "windows-sys 0.42.0",
+]
+
 [[package]]
 name = "quote"
 version = "1.0.21"
@@ -1299,6 +1438,27 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.3.1"
@@ -1314,6 +1474,15 @@ version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
 
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
 [[package]]
 name = "rayon"
 version = "1.6.0"
@@ -1372,6 +1541,21 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
 [[package]]
 name = "rustc-demangle"
 version = "0.1.21"
@@ -1393,6 +1577,39 @@ dependencies = [
  "semver",
 ]
 
+[[package]]
+name = "rustls"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
+dependencies = [
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
+dependencies = [
+ "base64",
+]
+
 [[package]]
 name = "rustversion"
 version = "1.0.9"
@@ -1414,6 +1631,16 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "schannel"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
+dependencies = [
+ "lazy_static",
+ "windows-sys 0.36.1",
+]
+
 [[package]]
 name = "scoped-tls"
 version = "1.0.0"
@@ -1432,6 +1659,39 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
 
+[[package]]
+name = "sct"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
 [[package]]
 name = "semver"
 version = "0.11.0"
@@ -1505,6 +1765,12 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
 
+[[package]]
+name = "signature"
+version = "1.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
+
 [[package]]
 name = "slab"
 version = "0.4.7"
@@ -1514,6 +1780,12 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
 [[package]]
 name = "socket2"
 version = "0.4.7"
@@ -1524,6 +1796,22 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "spki"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
 [[package]]
 name = "stable_deref_trait"
 version = "1.2.0"
@@ -1610,7 +1898,7 @@ version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
 dependencies = [
- "rand",
+ "rand 0.4.6",
  "remove_dir_all",
 ]
 
@@ -1648,18 +1936,18 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
 
 [[package]]
 name = "thiserror"
-version = "1.0.37"
+version = "1.0.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
+checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.37"
+version = "1.0.38"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
+checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1687,6 +1975,21 @@ dependencies = [
  "serde_json",
 ]
 
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
 [[package]]
 name = "tokio"
 version = "1.23.0"
@@ -1715,6 +2018,17 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "tokio-stream"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
 [[package]]
 name = "tokio-uring"
 version = "0.3.0"
@@ -1751,9 +2065,21 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
 dependencies = [
  "cfg-if",
  "pin-project-lite",
+ "tracing-attributes",
  "tracing-core",
 ]
 
+[[package]]
+name = "tracing-attributes"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "tracing-core"
 version = "0.1.30"
@@ -1820,6 +2146,12 @@ version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
 
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
 [[package]]
 name = "vcpkg"
 version = "0.2.15"
@@ -1939,6 +2271,16 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "webpki"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
 [[package]]
 name = "which"
 version = "4.3.0"
@@ -2081,6 +2423,22 @@ version = "0.42.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
 
+[[package]]
+name = "x509-certificate"
+version = "0.17.0"
+dependencies = [
+ "bcder",
+ "bytes",
+ "chrono",
+ "der",
+ "hex",
+ "pem",
+ "ring",
+ "signature",
+ "spki",
+ "thiserror",
+]
+
 [[package]]
 name = "zerocopy"
 version = "0.6.1"

+ 4 - 0
crates/btlib/Cargo.toml

@@ -29,6 +29,9 @@ env_logger = { version = "0.9.0" }
 chrono = "0.4.23"
 anyhow = { version = "1.0.66", features = ["std", "backtrace"] }
 positioned-io = "0.3.1"
+x509-certificate = { path = "../../../cryptography-rs/x509-certificate" }
+bcder = "0.7.1"
+bytes = "1.3.0"
 
 [dev-dependencies]
 tempdir = { version = "0.3.7" }
@@ -36,6 +39,7 @@ ctor = { version = "0.1.22" }
 lazy_static = { version = "1.4.0" }
 vm-memory = { version = "0.9.0" }
 criterion = "0.4.0"
+webpki = "0.22.0" 
 
 [[bench]]
 name = "block_benches"

+ 24 - 27
crates/btlib/src/block_path.rs

@@ -1,9 +1,11 @@
+use crate::Principal;
+use serde::{Deserialize, Serialize};
+use std::fmt::Display;
+
 pub use private::{BlockPath, BlockPathError};
 
 mod private {
-    use crate::{crypto::VarHash, Principal};
-    use serde::{Deserialize, Serialize};
-    use std::fmt::Display;
+    use super::*;
 
     /// An identifier for a block in a tree.
     #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Default)]
@@ -72,9 +74,8 @@ mod private {
             if self.components.len() > other.components.len() {
                 return false;
             }
-            // Skip the component containing the owner.
-            let self_iter = self.components.iter().skip(1);
-            let other_iter = other.components.iter().skip(1);
+            let self_iter = self.components.iter();
+            let other_iter = other.components.iter();
             for pair in self_iter.zip(other_iter) {
                 if pair.0 != pair.1 {
                     return false;
@@ -114,6 +115,15 @@ mod private {
         fn try_from(string: &'s str) -> std::result::Result<BlockPath, BlockPathError> {
             BlockPath::assert_not_too_long(string)?;
             let mut pairs = string.char_indices();
+            let root = match pairs.next() {
+                Some((start, c)) => {
+                    let end = BlockPath::component_end(start, c, &mut pairs)?;
+                    let slice = &string[start..end];
+                    Principal::try_from(slice)
+                        .map_err(|_| BlockPathError::InvalidLeadingComponent)?
+                }
+                None => return Err(BlockPathError::InvalidLeadingComponent),
+            };
             let mut components = Vec::new();
             let mut last_end = 0;
             while let Some((start, c)) = pairs.next() {
@@ -126,27 +136,14 @@ mod private {
             if string.len() - 1 == last_end {
                 components.push("".to_string());
             }
-            let leading = components
-                .get(0)
-                .ok_or(BlockPathError::InvalidLeadingComponent)?;
-            let hash = VarHash::try_from(leading.as_str())
-                .map_err(|_| BlockPathError::InvalidLeadingComponent)?;
-            Ok(BlockPath {
-                root: Principal(hash),
-                components,
-            })
+            Ok(BlockPath { root, components })
         }
     }
 
     impl Display for BlockPath {
         fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-            if self.components.is_empty() {
-                return write!(f, "");
-            };
-            let mut iter = self.components.iter();
-            let first = iter.next().unwrap();
-            let mut output = write!(f, "{first}");
-            for component in iter {
+            let mut output = write!(f, "{}", self.root);
+            for component in self.components.iter() {
                 output = write!(f, "{}{component}", BlockPath::SEP)
             }
             output
@@ -188,14 +185,13 @@ mod private {
 
 #[cfg(test)]
 mod tests {
+    use super::*;
+
     use crate::{
-        crypto::VarHash,
         test_helpers::{make_path, make_principal, PRINCIPAL2},
-        Principal,
+        VarHash,
     };
 
-    use super::*;
-
     fn path_from_str_test_case(
         expected: std::result::Result<BlockPath, BlockPathError>,
         input: &str,
@@ -258,7 +254,8 @@ mod tests {
     #[test]
     fn path_round_trip() -> std::result::Result<(), BlockPathError> {
         let expected = make_path(vec!["interstitial", "inter-related", "intersections"]);
-        let actual = BlockPath::try_from(expected.to_string().as_str())?;
+        let string = expected.to_string();
+        let actual = BlockPath::try_from(string.as_str())?;
         assert_eq!(expected, actual);
         Ok(())
     }

+ 9 - 0
crates/btlib/src/crypto/mod.rs → crates/btlib/src/crypto.rs

@@ -1,6 +1,7 @@
 pub mod tpm;
 
 pub mod merkle_stream;
+pub mod x509;
 pub use merkle_stream::MerkleStream;
 pub mod secret_stream;
 pub use secret_stream::SecretStream;
@@ -1306,6 +1307,10 @@ impl<S: Scheme> AsymKey<Private, S> {
         let pkey = scheme.private_from_der(der)?;
         Ok(AsymKey { scheme, pkey })
     }
+
+    pub fn to_der(&self) -> Result<Vec<u8>> {
+        self.pkey.private_key_to_der().map_err(|err| err.into())
+    }
 }
 
 impl<'de, S: Scheme> Deserialize<'de> for AsymKey<Public, S> {
@@ -1571,6 +1576,10 @@ impl ConcreteCreds {
     pub fn set_writecap(&mut self, writecap: Writecap) {
         self.writecap = Some(writecap)
     }
+
+    pub fn private_sign(&self) -> &AsymKey<Private, Sign> {
+        &self.sign.private
+    }
 }
 
 impl Verifier for ConcreteCreds {

+ 368 - 0
crates/btlib/src/crypto/x509.rs

@@ -0,0 +1,368 @@
+//! Code for converting [Writecap]s to and from the X.509 certificate format.
+
+use crate::{
+    bterr,
+    crypto::{AsymKeyPub, HashKind, RsaSsaPss, Sha2_256, Sha2_512, Sign},
+    Principaled, Result, Writecap,
+};
+use bcder::{
+    decode::{BytesSource, Constructed},
+    encode::{PrimitiveContent, Values},
+    BitString, Captured, Ia5String, Integer, Mode, OctetString, Oid, Tag,
+};
+use bytes::{BufMut, Bytes, BytesMut};
+use chrono::{offset::Utc, TimeZone};
+use x509_certificate::{
+    asn1time::{Time, UtcTime},
+    certificate::X509Certificate,
+    rfc3280::Name,
+    rfc5280::{
+        AlgorithmIdentifier, AlgorithmParameter, Certificate, CertificateSerialNumber, Extension,
+        Extensions, SubjectPublicKeyInfo, TbsCertificate, Validity, Version,
+    },
+};
+mod private {
+    use super::*;
+
+    impl Sha2_256 {
+        // The DER encoding of the OID 2.16.840.1.101.3.4.2.1
+        const OID: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01];
+    }
+
+    impl Sha2_512 {
+        // The DER encoding of the OID 2.16.840.1.101.3.4.2.3
+        const OID: &[u8] = &[0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03];
+    }
+
+    impl HashKind {
+        const fn oid(&self) -> &'static [u8] {
+            match self {
+                HashKind::Sha2_256 => Sha2_256::OID,
+                HashKind::Sha2_512 => Sha2_512::OID,
+            }
+        }
+    }
+
+    /// The DER encoding of 1.2.840.113549.1.1.8 (id-mgf1 in RFC 4055)
+    const MGF1_OID: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x08];
+
+    struct NullTermOid {
+        oid: Oid,
+    }
+
+    impl NullTermOid {
+        fn new(oid: Oid) -> Self {
+            Self { oid }
+        }
+
+        fn encode_ref(&self) -> impl Values + '_ {
+            self.encode_ref_as(Tag::SEQUENCE)
+        }
+
+        fn encode_ref_as(&self, tag: Tag) -> impl Values + '_ {
+            bcder::encode::sequence_as(
+                tag,
+                (
+                    self.oid.encode_ref(),
+                    bcder::encode::Constructed::new(Tag::NULL, bcder::encode::Nothing),
+                ),
+            )
+        }
+    }
+
+    struct AlgoId {
+        algorithm: Oid,
+        parameters: NullTermOid,
+    }
+
+    impl AlgoId {
+        fn new(hash_kind: HashKind) -> Self {
+            let algorithm = Oid(Bytes::from(MGF1_OID));
+            let parameters = NullTermOid::new(Oid(Bytes::from(hash_kind.oid())));
+            Self {
+                algorithm,
+                parameters,
+            }
+        }
+
+        fn encode_ref(&self) -> impl Values + '_ {
+            self.encode_ref_as(Tag::SEQUENCE)
+        }
+
+        fn encode_ref_as(&self, tag: Tag) -> impl Values + '_ {
+            bcder::encode::sequence_as(
+                tag,
+                (self.algorithm.encode_ref(), self.parameters.encode_ref()),
+            )
+        }
+    }
+
+    struct RsaSsaPssParams {
+        hash_algorithm: NullTermOid,
+        mask_gen_algorithm: AlgoId,
+        salt_length: Integer,
+        trailer_field: Option<Integer>,
+    }
+
+    impl RsaSsaPssParams {
+        fn new(hash_kind: HashKind) -> Self {
+            let hash_algorithm = NullTermOid::new(Oid(Bytes::from(hash_kind.oid())));
+            let mask_gen_algorithm = AlgoId::new(hash_kind);
+            let salt_length = Integer::from(hash_kind.len() as u64);
+            Self {
+                hash_algorithm,
+                mask_gen_algorithm,
+                salt_length,
+                trailer_field: None,
+            }
+        }
+
+        fn encode_ref(&self) -> impl bcder::encode::Values + '_ {
+            self.encode_ref_as(bcder::Tag::SEQUENCE)
+        }
+
+        fn encode_ref_as(&self, tag: Tag) -> impl bcder::encode::Values + '_ {
+            use bcder::encode::Constructed;
+            bcder::encode::sequence_as(
+                tag,
+                (
+                    Constructed::new(Tag::CTX_0, self.hash_algorithm.encode_ref()),
+                    Constructed::new(Tag::CTX_1, self.mask_gen_algorithm.encode_ref()),
+                    Constructed::new(Tag::CTX_2, self.salt_length.encode()),
+                    self.trailer_field
+                        .as_ref()
+                        .map(|e| Constructed::new(Tag::CTX_3, e.encode())),
+                ),
+            )
+        }
+    }
+
+    impl RsaSsaPss {
+        const USE_PSS_OID: bool = false;
+
+        /// The OID 1.2.840.113549.1.1.10 (RSASSA-PSS)
+        const RSA_PSS_OID: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0A];
+
+        /// The OID 1.2.840.113549.1.1.1 (RSA-ES)
+        const RSA_ES_OID: &[u8] = &[0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01];
+
+        const fn oid(&self) -> &'static [u8] {
+            if Self::USE_PSS_OID {
+                Self::RSA_PSS_OID
+            } else {
+                Self::RSA_ES_OID
+            }
+        }
+
+        fn params(&self) -> Result<Option<AlgorithmParameter>> {
+            if Self::USE_PSS_OID {
+                let params = RsaSsaPssParams::new(self.hash_kind);
+                let values = params.encode_ref();
+                let captured = Captured::from_values(bcder::Mode::Der, values);
+                Ok(Some(AlgorithmParameter::from_captured(captured)))
+            } else {
+                Ok(None)
+            }
+        }
+    }
+
+    impl Sign {
+        const fn oid(&self) -> &'static [u8] {
+            match self {
+                Sign::RsaSsaPss(inner) => inner.oid(),
+            }
+        }
+
+        fn params(&self) -> Result<Option<AlgorithmParameter>> {
+            match self {
+                Sign::RsaSsaPss(inner) => inner.params(),
+            }
+        }
+    }
+
+    fn scheme_to_algo_id(scheme: &Sign) -> Result<AlgorithmIdentifier> {
+        Ok(AlgorithmIdentifier {
+            algorithm: oid(scheme.oid()),
+            parameters: scheme.params()?,
+        })
+    }
+
+    fn oid(slice: &'static [u8]) -> Oid {
+        Oid(Bytes::from(slice))
+    }
+
+    macro_rules! bit_string {
+        ($bytes:expr) => {
+            BitString::new(0, Bytes::from($bytes))
+        };
+    }
+
+    /// OID 2.5.29.17 (Subject Alternative Name)
+    const SAN_OID: &[u8] = &[0x55, 0x1D, 0x11];
+
+    struct SubjectAltName {
+        issued_to: Ia5String,
+    }
+
+    impl SubjectAltName {
+        fn new(issued_to: String) -> Result<Self> {
+            let issued_to = Ia5String::from_string(issued_to).map_err(|err| bterr!("{:?}", err))?;
+            Ok(Self { issued_to })
+        }
+
+        fn encode(&self) -> impl Values + '_ {
+            self.encode_as(Tag::SEQUENCE)
+        }
+
+        fn encode_as(&self, tag: Tag) -> impl Values + '_ {
+            bcder::encode::sequence_as(tag, self.issued_to.encode_ref())
+        }
+
+        fn encode_der(&self) -> Result<Bytes> {
+            let mut writer = BytesMut::new().writer();
+            self.encode().write_encoded(bcder::Mode::Der, &mut writer)?;
+            Ok(writer.into_inner().into())
+        }
+    }
+
+    impl AsymKeyPub<Sign> {
+        fn subject_public_key_info(&self) -> Result<SubjectPublicKeyInfo> {
+            Ok(SubjectPublicKeyInfo {
+                algorithm: scheme_to_algo_id(&self.scheme)?,
+                subject_public_key: self.to_bit_string()?,
+            })
+        }
+
+        fn to_bit_string(&self) -> Result<BitString> {
+            let der = self.pkey.public_key_to_der()?;
+            let source = BytesSource::new(Bytes::from(der));
+            let spki = Constructed::decode(source, Mode::Der, SubjectPublicKeyInfo::take_from)?;
+            Ok(spki.subject_public_key)
+        }
+
+        pub fn to_der(&self) -> Result<Vec<u8>> {
+            let spki = self.subject_public_key_info()?;
+            let mut vec = Vec::new();
+            spki.encode_ref().write_encoded(Mode::Der, &mut vec)?;
+            Ok(vec)
+        }
+    }
+
+    impl Writecap {
+        pub fn to_cert_chain(
+            &self,
+            subject_key: &AsymKeyPub<Sign>,
+        ) -> Result<Vec<X509Certificate>> {
+            let mut chain = match self.next.as_ref() {
+                Some(next) => next.as_ref().to_cert_chain(&self.body.signing_key)?,
+                None => Vec::new(),
+            };
+
+            let version = Some(Version::V3);
+            let serial_number = CertificateSerialNumber::from(1);
+            let signature_algorithm = scheme_to_algo_id(&self.body.signing_key.scheme)?;
+            let mut issuer = Name::default();
+            issuer
+                .append_common_name_utf8_string(&self.body.signing_key.principal().to_string())
+                .map_err(|_| bterr!("failed to create issuer common name"))?;
+            let expires = Utc
+                .timestamp_millis_opt(1000 * self.body.expires.to_unix())
+                .single()
+                .ok_or_else(|| {
+                    bterr!("failed to convert writecap expiration to chrono DataTime")
+                })?;
+            let validity = Validity {
+                not_before: Time::UtcTime(UtcTime::now()),
+                not_after: Time::from(expires),
+            };
+            let issued_to = self.body.issued_to.to_string();
+            let mut subject = Name::default();
+            subject
+                .append_common_name_utf8_string(&issued_to)
+                .map_err(|_| bterr!("failed to create subject common name"))?;
+            let subject_public_key_info = subject_key.subject_public_key_info()?;
+            let mut extensions = Extensions::default();
+            let san = SubjectAltName::new(issued_to)?;
+            extensions.push(Extension {
+                id: oid(SAN_OID),
+                critical: Some(false),
+                value: OctetString::new(san.encode_der()?),
+            });
+            let tbs_certificate = TbsCertificate {
+                version,
+                serial_number,
+                signature: signature_algorithm.clone(),
+                issuer,
+                validity,
+                subject,
+                subject_public_key_info,
+                issuer_unique_id: None,
+                subject_unique_id: None,
+                extensions: Some(extensions),
+                raw_data: None,
+            };
+            let cert = Certificate {
+                tbs_certificate,
+                signature_algorithm,
+                signature: bit_string!(self.signature.data.clone()),
+            };
+            chain.push(cert.into());
+            Ok(chain)
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use webpki::EndEntityCert;
+
+    use crate::test_helpers::node_creds;
+
+    #[allow(dead_code)]
+    fn save_first_writecap_der_to_file() {
+        let node_creds = node_creds();
+        let chain = node_creds
+            .writecap
+            .as_ref()
+            .unwrap()
+            .to_cert_chain(&node_creds.sign.public)
+            .unwrap();
+        let first = chain.first().unwrap();
+        let mut buf = Vec::new();
+
+        first.encode_der_to(&mut buf).unwrap();
+        std::fs::write("/tmp/cert.der", buf).unwrap();
+
+        let pem = first.encode_pem().unwrap();
+        std::fs::write("/tmp/cert.pem", &pem).unwrap();
+    }
+
+    #[test]
+    fn node_writecap_to_cert_chain() {
+        let node_creds = node_creds();
+
+        let result = node_creds
+            .writecap
+            .as_ref()
+            .unwrap()
+            .to_cert_chain(&node_creds.sign.public);
+
+        assert!(result.is_ok())
+    }
+
+    #[test]
+    fn node_writecap_to_cert_chain_end_cert_can_be_parsed() {
+        let node_creds = node_creds();
+        let chain = node_creds
+            .writecap
+            .as_ref()
+            .unwrap()
+            .to_cert_chain(&node_creds.sign.public)
+            .unwrap();
+        let der = chain.first().unwrap().encode_der().unwrap();
+
+        let result = EndEntityCert::try_from(der.as_slice());
+
+        result.unwrap();
+    }
+}

+ 8 - 4
crates/btlib/src/lib.rs

@@ -1310,11 +1310,15 @@ impl Epoch {
     }
 
     pub fn from_value(value: u64) -> Self {
-        value.into()
+        Self(value)
     }
 
     pub fn value(self) -> u64 {
-        self.into()
+        self.0
+    }
+
+    pub fn to_unix(self) -> libc::time_t {
+        self.0 as libc::time_t
     }
 }
 
@@ -1322,13 +1326,13 @@ impl Copy for Epoch {}
 
 impl From<u64> for Epoch {
     fn from(value: u64) -> Self {
-        Self(value)
+        Epoch::from_value(value)
     }
 }
 
 impl From<Epoch> for u64 {
     fn from(value: Epoch) -> Self {
-        value.0
+        value.value()
     }
 }
 

+ 1 - 2
crates/btlib/src/test_helpers.rs

@@ -122,8 +122,7 @@ pub fn make_principal() -> Principal {
 }
 
 pub fn make_path_with_root(root: Principal, rel_components: Vec<&str>) -> BlockPath {
-    let mut components = Vec::with_capacity(rel_components.len() + 1);
-    components.push(root.0.to_string());
+    let mut components = Vec::with_capacity(rel_components.len());
     for component in rel_components {
         components.push(component.to_string());
     }

+ 8 - 2
crates/btmsg/Cargo.toml

@@ -11,10 +11,16 @@ btserde = { path = "../btserde" }
 serde = { version = "^1.0.136", features = ["derive"] }
 bytes = "1.3.0"
 futures = "0.3.25"
-tokio = { version = "1.23.0", features = ["net", "io-util", "macros"] }
+tokio = { version = "1.23.0", features = ["net", "io-util", "macros", "sync"] }
 tokio-util = { version = "0.7.4", features = ["codec"] }
+tokio-stream = "0.1.11"
 zerocopy = "0.6.1"
 lazy_static = { version = "1.4.0" }
+quinn = "0.9.3"
+rustls = { version = "0.20.7", features = ["dangerous_configuration"] }
+anyhow = { version = "1.0.66", features = ["std", "backtrace"] }
+log = "0.4.17"
 
 [dev-dependencies]
-tempdir = { version = "0.3.7" }
+env_logger = "0.9.0"
+ctor = { version = "0.1.22" }

+ 308 - 50
crates/btmsg/src/lib.rs

@@ -1,5 +1,10 @@
 //! Code which enables sending messages between processes in the blocktree system.
-use btlib::{crypto::rand_array, error::BoxInIoErr, Principal, Result};
+use btlib::{
+    bterr,
+    crypto::{rand_array, ConcreteCreds, CredsPriv, CredsPub},
+    error::BoxInIoErr,
+    Principal, Result,
+};
 use btserde::{read_from, write_to};
 use bytes::{BufMut, BytesMut};
 use core::{
@@ -8,24 +13,33 @@ use core::{
     task::{Context, Poll},
 };
 use futures::{
-    sink::{Send, Sink},
+    sink::{Close, Send, Sink},
     stream::Stream,
     SinkExt, StreamExt,
 };
 use lazy_static::lazy_static;
+use log::error;
+use quinn::{ClientConfig, Endpoint, SendStream, ServerConfig};
+use rustls::{Certificate, PrivateKey, ConfigBuilder, ConfigSide, WantsCipherSuites, WantsVerifier};
 use serde::{Deserialize, Serialize};
 use std::{
+    collections::hash_map::DefaultHasher,
+    hash::{Hash, Hasher},
     io,
     marker::PhantomData,
-    net::Shutdown,
+    net::{IpAddr, Ipv6Addr, Shutdown, SocketAddr},
     path::PathBuf,
-    collections::hash_map::DefaultHasher,
-    hash::{Hash, Hasher},
+    sync::Arc,
 };
 use tokio::{
     io::{AsyncRead, AsyncWrite, ReadBuf},
     net::UnixDatagram,
+    sync::{
+        broadcast::{self, error::TryRecvError},
+        mpsc,
+    },
 };
+use tokio_stream::wrappers::ReceiverStream;
 use tokio_util::codec::{Decoder, Encoder, FramedRead, FramedWrite};
 use zerocopy::FromBytes;
 
@@ -35,6 +49,24 @@ mod private {
 
     use super::*;
 
+    /// Returns a [Receiver] which can be used to receive messages addressed to the given path.
+    /// The `fs_path` argument specifies the filesystem directory under which the receiver's socket
+    /// will be stored.
+    pub fn local_receiver<T: for<'de> Deserialize<'de> + core::marker::Send + 'static>(
+        addr: BlockAddr,
+        creds: &ConcreteCreds,
+    ) -> Result<impl Receiver<T>> {
+        QuicReceiver::new(addr, creds)
+    }
+
+    /// Returns a [Sender] which can be used to send messages to the given Blocktree path.
+    /// The `fs_path` argument specifies the filesystem directory in which to locate the
+    /// socket of the recipient.
+    pub async fn local_sender(addr: BlockAddr) -> Result<impl Sender> {
+        let result = QuicSender::new(addr).await;
+        result
+    }
+
     lazy_static! {
         /// The default directory in which to place blocktree sockets.
         static ref SOCK_DIR: PathBuf = {
@@ -45,10 +77,42 @@ mod private {
     }
 
     /// Appends the given Blocktree path to the path of the given directory.
+    #[allow(dead_code)]
     fn socket_path(fs_path: &mut PathBuf, addr: &BlockAddr) {
         fs_path.push(addr.num.value().to_string());
     }
 
+    fn common_config<Side: ConfigSide>(
+        builder: ConfigBuilder<Side, WantsCipherSuites>,
+    ) -> Result<ConfigBuilder<Side, WantsVerifier>> {
+        builder
+            .with_cipher_suites(&[rustls::cipher_suite::TLS13_AES_128_GCM_SHA256])
+            .with_kx_groups(&[&rustls::kx_group::SECP256R1])
+            .with_protocol_versions(&[&rustls::version::TLS13])
+            .map_err(|err| err.into())
+    }
+
+    fn server_config(creds: &ConcreteCreds) -> Result<ServerConfig> {
+        let writecap = creds.writecap().ok_or(btlib::BlockError::MissingWritecap)?;
+        let chain = writecap.to_cert_chain(creds.public_sign())?;
+        let mut cert_chain = Vec::with_capacity(chain.len());
+        for cert in chain {
+            cert_chain.push(Certificate(cert.encode_der()?))
+        }
+        let key = PrivateKey(creds.private_sign().to_der()?);
+        let server_config = common_config(rustls::ServerConfig::builder())?
+            .with_no_client_auth()
+            .with_single_cert(cert_chain, key)?;
+        Ok(ServerConfig::with_crypto(Arc::new(server_config)))
+    }
+
+    fn client_config() -> Result<ClientConfig> {
+        let client_config = common_config(rustls::ClientConfig::builder())?
+            .with_custom_certificate_verifier(CertVerifier::new())
+            .with_no_client_auth();
+        Ok(ClientConfig::new(Arc::new(client_config)))
+    }
+
     /// An identifier for a block. Persistent blocks (files, directories, and servers) are
     /// identified by the `Inode` variant and transient blocks (processes) are identified by the
     /// PID variant.
@@ -132,6 +196,8 @@ mod private {
     }
 
     /// A type which can be used to send messages.
+    /// Once the "Permit impl Trait in type aliases" https://github.com/rust-lang/rust/issues/63063
+    /// feature lands the future types in this trait should be rewritten to use it.
     pub trait Sender {
         type SendFut<'a, T>: 'a + Future<Output = Result<()>> + core::marker::Send
         where
@@ -143,14 +209,18 @@ mod private {
             msg: Msg<T>,
         ) -> Self::SendFut<'a, T>;
 
+        type FinishFut: Future<Output = Result<()>> + core::marker::Send;
+
+        fn finish(self) -> Self::FinishFut;
+
         fn addr(&self) -> &BlockAddr;
 
         fn send_msg<'a, T: 'a + Serialize + core::marker::Send>(
             &'a mut self,
-            to: BlockAddr,
+            from: BlockAddr,
             body: T,
         ) -> Self::SendFut<'a, T> {
-            let msg = Msg::with_rand_id(to, self.addr().clone(), body).unwrap();
+            let msg = Msg::with_rand_id(self.addr().clone(), from, body).unwrap();
             self.send(msg)
         }
     }
@@ -163,6 +233,12 @@ mod private {
     /// Encodes and decodes messages using [btserde].
     struct MsgEncoder;
 
+    impl MsgEncoder {
+        fn new() -> Self {
+            Self
+        }
+    }
+
     impl<T: Serialize> Encoder<Msg<T>> for MsgEncoder {
         type Error = btlib::Error;
 
@@ -183,6 +259,12 @@ mod private {
 
     struct MsgDecoder<T>(PhantomData<T>);
 
+    impl<T> MsgDecoder<T> {
+        fn new() -> Self {
+            Self(PhantomData)
+        }
+    }
+
     impl<T: for<'de> Deserialize<'de>> Decoder for MsgDecoder<T> {
         type Item = Msg<T>;
         type Error = btlib::Error;
@@ -218,6 +300,7 @@ mod private {
     }
 
     impl DatagramAdapter {
+        #[allow(dead_code)]
         fn new(socket: UnixDatagram) -> Self {
             Self { socket }
         }
@@ -278,6 +361,7 @@ mod private {
     }
 
     impl<T: for<'de> Deserialize<'de>> UnixReceiver<T> {
+        #[allow(dead_code)]
         fn new(mut fs_path: PathBuf, addr: BlockAddr) -> Result<Self> {
             socket_path(&mut fs_path, &addr);
             let socket = DatagramAdapter::new(UnixDatagram::bind(fs_path)?);
@@ -300,16 +384,6 @@ mod private {
         }
     }
 
-    /// Returns a [Receiver] which can be used to receive messages addressed to the given path.
-    /// The `fs_path` argument specifies the filesystem directory under which the receiver's socket
-    /// will be stored.
-    pub fn local_receiver<T: for<'de> Deserialize<'de>>(
-        fs_path: PathBuf,
-        addr: BlockAddr,
-    ) -> Result<impl Receiver<T>> {
-        UnixReceiver::new(fs_path, addr)
-    }
-
     /// An implementation of [Sender] which uses a Unix datagram socket to send messages.
     struct UnixSender {
         addr: BlockAddr,
@@ -317,6 +391,7 @@ mod private {
     }
 
     impl UnixSender {
+        #[allow(dead_code)]
         fn new(mut fs_path: PathBuf, addr: BlockAddr) -> Result<Self> {
             let socket = UnixDatagram::unbound()?;
             socket_path(&mut fs_path, &addr);
@@ -367,13 +442,182 @@ mod private {
         ) -> Self::SendFut<'a, T> {
             self.socket.send(msg)
         }
+
+        type FinishFut = Pin<Box<dyn Future<Output = Result<()>> + core::marker::Send>>;
+
+        fn finish(mut self) -> Self::FinishFut {
+            Box::pin(async move {
+                let fut: Close<'_, _, Msg<()>> = self.socket.close();
+                fut.await
+            })
+        }
     }
 
-    /// Returns a [Sender] which can be used to send messages to the given Blocktree path.
-    /// The `fs_path` argument specifies the filesystem directory in which to locate the
-    /// socket of the recipient.
-    pub fn local_sender(fs_path: PathBuf, addr: BlockAddr) -> Result<impl Sender> {
-        UnixSender::new(fs_path, addr)
+    /// Causes the current function to return if the given `rx` has received a stop signal.
+    macro_rules! check_stop {
+        ($rx:expr) => {
+            match $rx.try_recv() {
+                Ok(_) => return,
+                Err(err) => {
+                    if let TryRecvError::Closed = err {
+                        return;
+                    }
+                }
+            }
+        };
+    }
+
+    struct QuicReceiver<T> {
+        addr: BlockAddr,
+        stop_tx: broadcast::Sender<()>,
+        stream: ReceiverStream<Result<Msg<T>>>,
+    }
+
+    impl<T: for<'de> Deserialize<'de> + core::marker::Send + 'static> QuicReceiver<T> {
+        /// The size of the buffer to store received messages in.
+        const MSG_BUF_SZ: usize = 64;
+
+        fn new(addr: BlockAddr, creds: &ConcreteCreds) -> Result<Self> {
+            let config = server_config(creds)?;
+            let port = addr.port();
+            let socket_addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port);
+            let endpoint = Endpoint::server(config, socket_addr)?;
+            let (stop_tx, mut stop_rx) = broadcast::channel(1);
+            let (msg_tx, msg_rx) = mpsc::channel(Self::MSG_BUF_SZ);
+            tokio::spawn(async move {
+                loop {
+                    check_stop!(stop_rx);
+                    let connecting = match endpoint.accept().await {
+                        Some(connection) => connection,
+                        None => break,
+                    };
+                    let connection = match connecting.await {
+                        Ok(connection) => connection,
+                        Err(err) => {
+                            error!("error accepting QUIC connection: {err}");
+                            continue;
+                        }
+                    };
+                    let conn_msg_tx = msg_tx.clone();
+                    let mut conn_stop_rx = stop_rx.resubscribe();
+                    tokio::spawn(async move {
+                        let recv_stream = match connection.accept_uni().await {
+                            Ok(recv_stream) => recv_stream,
+                            Err(err) => {
+                                error!("error accepting receive stream: {err}");
+                                return;
+                            }
+                        };
+                        let mut msg_stream = FramedRead::new(recv_stream, MsgDecoder::new());
+                        loop {
+                            check_stop!(conn_stop_rx);
+                            let result = match msg_stream.next().await {
+                                Some(result) => result,
+                                None => return,
+                            };
+                            if let Err(err) = conn_msg_tx.send(result).await {
+                                error!("error sending message to mpsc queue: {err}");
+                            }
+                        }
+                    });
+                }
+            });
+            Ok(Self {
+                addr,
+                stop_tx,
+                stream: ReceiverStream::new(msg_rx),
+            })
+        }
+    }
+
+    impl<T> Drop for QuicReceiver<T> {
+        fn drop(&mut self) {
+            // This result will be a failure if the tasks have already returned, which is not a
+            // problem.
+            let _ = self.stop_tx.send(());
+        }
+    }
+
+    impl<T: for<'de> Deserialize<'de> + core::marker::Send + 'static> Stream for QuicReceiver<T> {
+        type Item = Result<Msg<T>>;
+
+        fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+            self.stream.poll_next_unpin(cx)
+        }
+    }
+
+    impl<T: for<'de> Deserialize<'de> + core::marker::Send + 'static> Receiver<T> for QuicReceiver<T> {
+        fn addr(&self) -> &BlockAddr {
+            &self.addr
+        }
+    }
+
+    struct CertVerifier;
+
+    impl CertVerifier {
+        fn new() -> Arc<Self> {
+            Arc::new(Self)
+        }
+    }
+
+    impl rustls::client::ServerCertVerifier for CertVerifier {
+        fn verify_server_cert(
+            &self,
+            _end_entity: &Certificate,
+            _intermediates: &[Certificate],
+            _server_name: &rustls::ServerName,
+            _scts: &mut dyn Iterator<Item = &[u8]>,
+            _ocsp_response: &[u8],
+            _now: std::time::SystemTime,
+        ) -> std::result::Result<rustls::client::ServerCertVerified, rustls::Error> {
+            // TODO: Implement certificate verification.
+            Ok(rustls::client::ServerCertVerified::assertion())
+        }
+    }
+
+    struct QuicSender {
+        addr: BlockAddr,
+        sink: FramedWrite<SendStream, MsgEncoder>,
+    }
+
+    impl QuicSender {
+        async fn new(addr: BlockAddr) -> Result<Self> {
+            let mut endpoint =
+                Endpoint::client(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0))?;
+            endpoint.set_default_client_config(client_config()?);
+            let port = addr.port();
+            let socket_addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), port);
+            let connecting = endpoint.connect(socket_addr, "localhost")?;
+            let connection = connecting.await?;
+            let send_stream = connection.open_uni().await?;
+            let sink = FramedWrite::new(send_stream, MsgEncoder::new());
+            Ok(Self { addr, sink })
+        }
+    }
+
+    impl Sender for QuicSender {
+        fn addr(&self) -> &BlockAddr {
+            &self.addr
+        }
+
+        type SendFut<'a, T> = Send<'a, FramedWrite<SendStream, MsgEncoder>, Msg<T>>
+            where T: 'a + Serialize + core::marker::Send;
+
+        fn send<'a, T: 'a + Serialize + core::marker::Send>(
+            &'a mut self,
+            msg: Msg<T>,
+        ) -> Self::SendFut<'a, T> {
+            self.sink.send(msg)
+        }
+
+        type FinishFut = Pin<Box<dyn Future<Output = Result<()>> + core::marker::Send>>;
+
+        fn finish(mut self) -> Self::FinishFut {
+            Box::pin(async move {
+                let steam: &mut SendStream = self.sink.get_mut();
+                steam.finish().await.map_err(|err| bterr!(err))
+            })
+        }
     }
 
     /// This is an identify function which allows you to specify a type parameter for the output
@@ -392,11 +636,34 @@ mod private {
 mod tests {
     use super::*;
 
-    use tempdir::TempDir;
+    use btlib::{crypto::Creds, Epoch, Principaled};
+    use ctor::ctor;
+    use std::{
+        sync::atomic::{AtomicU64, Ordering},
+        time::Duration,
+    };
+
+    #[ctor]
+    fn setup_logging() {
+        env_logger::init();
+    }
 
     lazy_static! {
-        static ref ROOT_PRINCIPAL: Principal =
-            Principal::try_from("0!dSip4J0kurN5VhVo_aTipM-ywOOWrqJuRRVQ7aa-bew").unwrap();
+        static ref ROOT_CREDS: ConcreteCreds = ConcreteCreds::generate().unwrap();
+        static ref NODE_CREDS: ConcreteCreds = {
+            let mut creds = ConcreteCreds::generate().unwrap();
+            let root_creds = &ROOT_CREDS;
+            let writecap = root_creds
+                .issue_writecap(
+                    creds.principal(),
+                    vec![],
+                    Epoch::now() + Duration::from_secs(3600),
+                )
+                .unwrap();
+            creds.set_writecap(writecap);
+            creds
+        };
+        static ref ROOT_PRINCIPAL: Principal = ROOT_CREDS.principal();
     }
 
     fn block_addr(generation: u64, inode: u64) -> BlockAddr {
@@ -427,38 +694,28 @@ mod tests {
     }
 
     struct TestCase {
-        dir: TempDir,
+        instance_num: u64,
     }
 
     impl TestCase {
         fn new() -> TestCase {
-            Self {
-                dir: TempDir::new("btmsg").unwrap(),
-            }
+            static INSTANCE_NUM: AtomicU64 = AtomicU64::new(0);
+            let instance_num = INSTANCE_NUM.fetch_add(1, Ordering::SeqCst);
+            Self { instance_num }
         }
 
-        fn endpoint(&self, inode: u64) -> (BlockAddr, impl Sender, impl Receiver<BodyOwned>) {
-            let addr = block_addr(1, inode);
-            let fs_path = self.dir.path().to_owned();
-            let receiver = local_receiver(fs_path.clone(), addr.clone()).unwrap();
-            let sender = local_sender(fs_path, addr.clone()).unwrap();
+        async fn endpoint(&self, inode: u64) -> (BlockAddr, impl Sender, impl Receiver<BodyOwned>) {
+            let addr = block_addr(self.instance_num, inode);
+            let receiver = local_receiver(addr.clone(), &NODE_CREDS).unwrap();
+            let sender = local_sender(addr.clone()).await.unwrap();
             (addr, sender, receiver)
         }
     }
 
-    /// This tests just server to ensure that changes to the port hashing algorithm are made
-    /// deliberately.
-    #[test]
-    fn hash_block_addr() {
-        let block_addr = BlockAddr::new(ROOT_PRINCIPAL.clone(), 1, BlockNum::Inode(1));
-        let port = block_addr.port();
-        assert_eq!(64984, port);
-    }
-
     #[tokio::test]
     async fn message_received_is_message_sent() {
         let case = TestCase::new();
-        let (addr, mut sender, mut receiver) = case.endpoint(1);
+        let (addr, mut sender, mut receiver) = case.endpoint(1).await;
 
         sender.send_msg(addr.clone(), BodyRef::Ping).await.unwrap();
         let actual = receiver.next().await.unwrap().unwrap();
@@ -476,10 +733,10 @@ mod tests {
     #[tokio::test]
     async fn ping_pong() {
         let case = TestCase::new();
-        let (addr_one, mut sender_one, mut receiver_one) = case.endpoint(1);
-        let (addr_two, mut sender_two, mut receiver_two) = case.endpoint(2);
+        let (addr_one, mut sender_one, mut receiver_one) = case.endpoint(1).await;
+        let (addr_two, mut sender_two, mut receiver_two) = case.endpoint(2).await;
 
-        let handle = tokio::spawn(async move {
+        tokio::spawn(async move {
             let msg = receiver_one.next().await.unwrap().unwrap();
             let reply_body = if let BodyOwned::Ping = msg.body {
                 BodyRef::Success
@@ -488,10 +745,10 @@ mod tests {
             };
             let fut = assert_send::<'_, Result<()>>(sender_two.send_msg(addr_one, reply_body));
             fut.await.unwrap();
+            sender_two.finish().await.unwrap();
         });
 
         sender_one.send_msg(addr_two, BodyRef::Ping).await.unwrap();
-        handle.await.unwrap();
         let reply = receiver_two.next().await.unwrap().unwrap();
         let matched = if let BodyOwned::Success = reply.body {
             true
@@ -504,8 +761,8 @@ mod tests {
     #[tokio::test]
     async fn read_write() {
         let case = TestCase::new();
-        let (addr_one, mut sender_one, mut receiver_one) = case.endpoint(1);
-        let (addr_two, mut sender_two, mut receiver_two) = case.endpoint(2);
+        let (addr_one, mut sender_one, mut receiver_one) = case.endpoint(1).await;
+        let (addr_two, mut sender_two, mut receiver_two) = case.endpoint(2).await;
 
         let handle = tokio::spawn(async move {
             let data: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
@@ -524,6 +781,7 @@ mod tests {
             let msg = Msg::new(msg.from, addr_one, msg.id, reply_body);
             let fut = assert_send::<'_, Result<()>>(sender_two.send(msg));
             fut.await.unwrap();
+            sender_two.finish().await.unwrap();
         });
 
         sender_one