diff --git a/.gitignore b/.gitignore index d240f9d6..4270d4ea 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,7 @@ _markdown_* **/result-* .direnv -/output \ No newline at end of file +# Visual studio code +.vscode + +/output diff --git a/Cargo.lock b/Cargo.lock index 858723b7..bc92f880 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1074,6 +1074,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "home" version = "0.5.9" @@ -1930,6 +1936,9 @@ dependencies = [ "criterion", "derive_builder 0.20.0", "env_logger", + "heck", + "hex", + "hex-literal", "home", "log", "memoffset 0.9.1", @@ -1948,10 +1957,12 @@ dependencies = [ "serial_test", "stacker", "static_assertions", + "tempfile", "test_bin", "thiserror", "toml", "zerocopy", + "zeroize", ] [[package]] @@ -2039,9 +2050,12 @@ version = "0.1.0" dependencies = [ "anyhow", "base64ct", + "mio 1.0.1", "rustix", "static_assertions", + "thiserror", "typenum", + "zerocopy", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index 44a6e325..555af420 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,13 +51,13 @@ arbitrary = { version = "1.3.2", features = ["derive"] } anyhow = { version = "1.0.86", features = ["backtrace", "std"] } mio = { version = "1.0.1", features = ["net", "os-poll"] } oqs-sys = { version = "0.9.1", default-features = false, features = [ - 'classic_mceliece', - 'kyber', + 'classic_mceliece', + 'kyber', ] } blake2 = "0.10.6" chacha20poly1305 = { version = "0.10.1", default-features = false, features = [ - "std", - "heapless", + "std", + "heapless", ] } zerocopy = { version = "0.7.35", features = ["derive"] } home = "0.5.9" @@ -65,6 +65,9 @@ derive_builder = "0.20.0" tokio = { version = "1.39", features = ["macros", "rt-multi-thread"] } postcard= {version = "1.0.8", features = ["alloc"]} libcrux = { version = "0.0.2-pre.2" } +hex-literal = { version = "0.4.1" } +hex = { version = "0.4.3" } +heck = { version = "0.5.0" } #Dev dependencies serial_test = "3.1.1" @@ -80,4 +83,4 @@ procspawn = {version = "1.0.0", features= ["test-support"]} #Broker dependencies (might need cleanup or changes) wireguard-uapi = { version = "3.0.0", features = ["xplatform"] } command-fds = "0.2.3" -rustix = { version = "0.38.27", features = ["net"] } +rustix = { version = "0.38.27", features = ["net", "fs"] } diff --git a/cipher-traits/src/kem.rs b/cipher-traits/src/kem.rs index f03f5f7a..5470736b 100644 --- a/cipher-traits/src/kem.rs +++ b/cipher-traits/src/kem.rs @@ -10,8 +10,6 @@ //! The [KEM] Trait describes the basic API offered by a Key Encapsulation //! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM]. -use std::result::Result; - /// Key Encapsulation Mechanism /// /// The KEM interface defines three operations: Key generation, key encapsulation and key diff --git a/ciphers/src/lib.rs b/ciphers/src/lib.rs index 25c9cb2a..58240b15 100644 --- a/ciphers/src/lib.rs +++ b/ciphers/src/lib.rs @@ -9,10 +9,12 @@ const_assert!(KEY_LEN == hash_domain::KEY_LEN); /// Authenticated encryption with associated data pub mod aead { - #[cfg(not(feature = "libcrux"))] - pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN}; - #[cfg(feature = "libcrux")] + #[cfg(not(feature = "experiment_libcrux"))] pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN}; + #[cfg(feature = "experiment_libcrux")] + pub use crate::subtle::chacha20poly1305_ietf_libcrux::{ + decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN, + }; } /// Authenticated encryption with associated data with a constant nonce diff --git a/ciphers/src/subtle/mod.rs b/ciphers/src/subtle/mod.rs index fc82773f..b7161647 100644 --- a/ciphers/src/subtle/mod.rs +++ b/ciphers/src/subtle/mod.rs @@ -1,7 +1,7 @@ pub mod blake2b; -#[cfg(not(feature = "libcrux"))] +#[cfg(not(feature = "experiment_libcrux"))] pub mod chacha20poly1305_ietf; -#[cfg(feature = "libcrux")] +#[cfg(feature = "experiment_libcrux")] pub mod chacha20poly1305_ietf_libcrux; pub mod incorrect_hmac_blake2b; pub mod xchacha20poly1305_ietf; diff --git a/constant-time/src/memcmp.rs b/constant-time/src/memcmp.rs index 9872028b..8039cc8f 100644 --- a/constant-time/src/memcmp.rs +++ b/constant-time/src/memcmp.rs @@ -7,18 +7,16 @@ /// /// The execution time of the function grows approx. linear with the length of the input. This is /// considered safe. -/// -/// ## Tests -/// [`tests::memcmp_runs_in_constant_time`] runs a stasticial test that the equality of the two -/// input parameters does not correlate with the run time. -/// -/// For discussion on how to (further) ensure the constant-time execution of this function, -/// see #[inline] pub fn memcmp(a: &[u8], b: &[u8]) -> bool { a.len() == b.len() && unsafe { memsec::memeq(a.as_ptr(), b.as_ptr(), a.len()) } } +/// [tests::memcmp_runs_in_constant_time] runs a stasticial test that the equality of the two +/// input parameters does not correlate with the run time. +/// +/// For discussion on how to (further) ensure the constant-time execution of this function, +/// see #[cfg(all(test, feature = "constant_time_tests"))] mod tests { use super::*; diff --git a/flake.nix b/flake.nix index 950dcbf0..4a546042 100644 --- a/flake.nix +++ b/flake.nix @@ -411,12 +411,12 @@ inherit (packages.proof-proverif) CRYPTOVERIF_LIB; inputsFrom = [ packages.default ]; nativeBuildInputs = with pkgs; [ + inputs.fenix.packages.${system}.complete.toolchain cmake # override the fakecmake from the main step above cargo-release clippy nodePackages.prettier nushell # for the .ci/gen-workflow-files.nu script - rustfmt packages.proverif-patched ]; }; diff --git a/fuzz/fuzz_targets/aead_enc_into.rs b/fuzz/fuzz_targets/aead_enc_into.rs index 19f0bc46..8f529f7a 100644 --- a/fuzz/fuzz_targets/aead_enc_into.rs +++ b/fuzz/fuzz_targets/aead_enc_into.rs @@ -15,8 +15,7 @@ pub struct Input { } fuzz_target!(|input: Input| { - let mut ciphertext: Vec = Vec::with_capacity(input.plaintext.len() + 16); - ciphertext.resize(input.plaintext.len() + 16, 0); + let mut ciphertext = vec![0u8; input.plaintext.len() + 16]; aead::encrypt( ciphertext.as_mut_slice(), diff --git a/rosenpass/Cargo.toml b/rosenpass/Cargo.toml index a90c458e..11cf7ecc 100644 --- a/rosenpass/Cargo.toml +++ b/rosenpass/Cargo.toml @@ -13,6 +13,15 @@ readme = "readme.md" name = "rosenpass" path = "src/main.rs" +[[bin]] +name = "rosenpass-gen-ipc-msg-types" +path = "src/bin/gen-ipc-msg-types.rs" +required-features = ["experiment_api", "internal_bin_gen_ipc_msg_types"] + +[[test]] +name = "api-integration-tests" +required-features = ["experiment_api", "internal_testing"] + [[bench]] name = "handshake" harness = false @@ -40,6 +49,10 @@ zerocopy = { workspace = true } home = { workspace = true } derive_builder = {workspace = true} rosenpass-wireguard-broker = {workspace = true} +zeroize = { workspace = true } +hex-literal = { workspace = true, optional = true } +hex = { workspace = true, optional = true } +heck = { workspace = true, optional = true } [build-dependencies] anyhow = { workspace = true } @@ -50,8 +63,12 @@ test_bin = { workspace = true } stacker = { workspace = true } serial_test = {workspace = true} procspawn = {workspace = true} +tempfile = { workspace = true } [features] enable_broker_api = ["rosenpass-wireguard-broker/enable_broker_api"] -enable_memfd_alloc = [] +experiment_memfd_secret = [] experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"] +experiment_api = ["hex-literal"] +internal_testing = [] +internal_bin_gen_ipc_msg_types = ["hex", "heck"] diff --git a/rosenpass/src/api/boilerplate/byte_slice_ext.rs b/rosenpass/src/api/boilerplate/byte_slice_ext.rs new file mode 100644 index 00000000..dd20b49b --- /dev/null +++ b/rosenpass/src/api/boilerplate/byte_slice_ext.rs @@ -0,0 +1,116 @@ +use zerocopy::{ByteSlice, Ref}; + +use rosenpass_util::zerocopy::{RefMaker, ZerocopySliceExt}; + +use super::{ + PingRequest, PingResponse, RawMsgType, RefMakerRawMsgTypeExt, RequestMsgType, RequestRef, + ResponseMsgType, ResponseRef, +}; + +pub trait ByteSliceRefExt: ByteSlice { + fn msg_type_maker(self) -> RefMaker { + self.zk_ref_maker() + } + + fn msg_type(self) -> anyhow::Result> { + self.zk_parse() + } + + fn msg_type_from_prefix(self) -> anyhow::Result> { + self.zk_parse_prefix() + } + + fn msg_type_from_suffix(self) -> anyhow::Result> { + self.zk_parse_suffix() + } + + fn request_msg_type(self) -> anyhow::Result { + self.msg_type_maker().parse_request_msg_type() + } + + fn request_msg_type_from_prefix(self) -> anyhow::Result { + self.msg_type_maker() + .from_prefix()? + .parse_request_msg_type() + } + + fn request_msg_type_from_suffix(self) -> anyhow::Result { + self.msg_type_maker() + .from_suffix()? + .parse_request_msg_type() + } + + fn response_msg_type(self) -> anyhow::Result { + self.msg_type_maker().parse_response_msg_type() + } + + fn response_msg_type_from_prefix(self) -> anyhow::Result { + self.msg_type_maker() + .from_prefix()? + .parse_response_msg_type() + } + + fn response_msg_type_from_suffix(self) -> anyhow::Result { + self.msg_type_maker() + .from_suffix()? + .parse_response_msg_type() + } + + fn parse_request(self) -> anyhow::Result> { + RequestRef::parse(self) + } + + fn parse_request_from_prefix(self) -> anyhow::Result> { + RequestRef::parse_from_prefix(self) + } + + fn parse_request_from_suffix(self) -> anyhow::Result> { + RequestRef::parse_from_suffix(self) + } + + fn parse_response(self) -> anyhow::Result> { + ResponseRef::parse(self) + } + + fn parse_response_from_prefix(self) -> anyhow::Result> { + ResponseRef::parse_from_prefix(self) + } + + fn parse_response_from_suffix(self) -> anyhow::Result> { + ResponseRef::parse_from_suffix(self) + } + + fn ping_request_maker(self) -> RefMaker { + self.zk_ref_maker() + } + + fn ping_request(self) -> anyhow::Result> { + self.zk_parse() + } + + fn ping_request_from_prefix(self) -> anyhow::Result> { + self.zk_parse_prefix() + } + + fn ping_request_from_suffix(self) -> anyhow::Result> { + self.zk_parse_suffix() + } + + fn ping_response_maker(self) -> RefMaker { + self.zk_ref_maker() + } + + fn ping_response(self) -> anyhow::Result> { + self.zk_parse() + } + + fn ping_response_from_prefix(self) -> anyhow::Result> { + self.zk_parse_prefix() + } + + fn ping_response_from_suffix(self) -> anyhow::Result> { + self.zk_parse_suffix() + } +} + +impl ByteSliceRefExt for B {} diff --git a/rosenpass/src/api/boilerplate/message_trait.rs b/rosenpass/src/api/boilerplate/message_trait.rs new file mode 100644 index 00000000..de4105b7 --- /dev/null +++ b/rosenpass/src/api/boilerplate/message_trait.rs @@ -0,0 +1,29 @@ +use zerocopy::{ByteSliceMut, Ref}; + +use rosenpass_util::zerocopy::RefMaker; + +use super::RawMsgType; + +pub trait Message { + type Payload; + type MessageClass: Into; + const MESSAGE_TYPE: Self::MessageClass; + + fn from_payload(payload: Self::Payload) -> Self; + fn init(&mut self); + fn setup(buf: B) -> anyhow::Result>; +} + +pub trait ZerocopyResponseMakerSetupMessageExt { + fn setup_msg(self) -> anyhow::Result>; +} + +impl ZerocopyResponseMakerSetupMessageExt for RefMaker +where + B: ByteSliceMut, + T: Message, +{ + fn setup_msg(self) -> anyhow::Result> { + T::setup(self.into_buf()) + } +} diff --git a/rosenpass/src/api/boilerplate/message_type.rs b/rosenpass/src/api/boilerplate/message_type.rs new file mode 100644 index 00000000..3a69564e --- /dev/null +++ b/rosenpass/src/api/boilerplate/message_type.rs @@ -0,0 +1,117 @@ +use hex_literal::hex; +use rosenpass_util::zerocopy::RefMaker; +use zerocopy::ByteSlice; + +use crate::RosenpassError::{self, InvalidApiMessageType}; + +pub type RawMsgType = u128; + +// constants generated by gen-ipc-msg-types: +// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Request +pub const PING_REQUEST: RawMsgType = + RawMsgType::from_le_bytes(hex!("2397 3ecc c441 704d 0b02 ea31 45d3 4999")); +// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Response +pub const PING_RESPONSE: RawMsgType = + RawMsgType::from_le_bytes(hex!("4ec7 f6f0 2bbc ba64 48f1 da14 c7cf 0260")); + +pub trait MessageAttributes { + fn message_size(&self) -> usize; +} + +#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] +pub enum RequestMsgType { + Ping, +} + +#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] +pub enum ResponseMsgType { + Ping, +} + +impl MessageAttributes for RequestMsgType { + fn message_size(&self) -> usize { + match self { + Self::Ping => std::mem::size_of::(), + } + } +} + +impl MessageAttributes for ResponseMsgType { + fn message_size(&self) -> usize { + match self { + Self::Ping => std::mem::size_of::(), + } + } +} + +impl TryFrom for RequestMsgType { + type Error = RosenpassError; + + fn try_from(value: RawMsgType) -> Result { + use RequestMsgType as E; + Ok(match value { + self::PING_REQUEST => E::Ping, + _ => return Err(InvalidApiMessageType(value)), + }) + } +} + +impl From for RawMsgType { + fn from(val: RequestMsgType) -> Self { + use RequestMsgType as E; + match val { + E::Ping => self::PING_REQUEST, + } + } +} + +impl TryFrom for ResponseMsgType { + type Error = RosenpassError; + + fn try_from(value: RawMsgType) -> Result { + use ResponseMsgType as E; + Ok(match value { + self::PING_RESPONSE => E::Ping, + _ => return Err(InvalidApiMessageType(value)), + }) + } +} + +impl From for RawMsgType { + fn from(val: ResponseMsgType) -> Self { + use ResponseMsgType as E; + match val { + E::Ping => self::PING_RESPONSE, + } + } +} + +pub trait RawMsgTypeExt { + fn into_request_msg_type(self) -> Result; + fn into_response_msg_type(self) -> Result; +} + +impl RawMsgTypeExt for RawMsgType { + fn into_request_msg_type(self) -> Result { + self.try_into() + } + + fn into_response_msg_type(self) -> Result { + self.try_into() + } +} + +pub trait RefMakerRawMsgTypeExt { + fn parse_request_msg_type(self) -> anyhow::Result; + fn parse_response_msg_type(self) -> anyhow::Result; +} + +impl RefMakerRawMsgTypeExt for RefMaker { + fn parse_request_msg_type(self) -> anyhow::Result { + Ok(self.parse()?.read().try_into()?) + } + + fn parse_response_msg_type(self) -> anyhow::Result { + Ok(self.parse()?.read().try_into()?) + } +} diff --git a/rosenpass/src/api/boilerplate/mod.rs b/rosenpass/src/api/boilerplate/mod.rs new file mode 100644 index 00000000..d60f0757 --- /dev/null +++ b/rosenpass/src/api/boilerplate/mod.rs @@ -0,0 +1,17 @@ +mod byte_slice_ext; +mod message_trait; +mod message_type; +mod payload; +mod request_ref; +mod request_response; +mod response_ref; +mod server; + +pub use byte_slice_ext::*; +pub use message_trait::*; +pub use message_type::*; +pub use payload::*; +pub use request_ref::*; +pub use request_response::*; +pub use response_ref::*; +pub use server::*; diff --git a/rosenpass/src/api/boilerplate/payload.rs b/rosenpass/src/api/boilerplate/payload.rs new file mode 100644 index 00000000..7537ee5b --- /dev/null +++ b/rosenpass/src/api/boilerplate/payload.rs @@ -0,0 +1,96 @@ +use rosenpass_util::zerocopy::ZerocopyMutSliceExt; +use zerocopy::{AsBytes, ByteSliceMut, FromBytes, FromZeroes, Ref}; + +use super::{Message, RawMsgType, RequestMsgType, ResponseMsgType}; + +/// Size required to fit any message in binary form +pub const MAX_REQUEST_LEN: usize = 2500; // TODO fix this +pub const MAX_RESPONSE_LEN: usize = 2500; // TODO fix this + +#[repr(packed)] +#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)] +pub struct Envelope { + /// Which message this is + pub msg_type: RawMsgType, + /// The actual Paylod + pub payload: M, +} + +pub type RequestEnvelope = Envelope; +pub type ResponseEnvelope = Envelope; + +#[repr(packed)] +#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)] +pub struct PingRequestPayload { + /// Randomly generated connection id + pub echo: [u8; 256], +} + +pub type PingRequest = RequestEnvelope; + +impl PingRequest { + pub fn new(echo: [u8; 256]) -> Self { + Self::from_payload(PingRequestPayload { echo }) + } +} + +impl Message for PingRequest { + type Payload = PingRequestPayload; + type MessageClass = RequestMsgType; + const MESSAGE_TYPE: Self::MessageClass = RequestMsgType::Ping; + + fn from_payload(payload: Self::Payload) -> Self { + Self { + msg_type: Self::MESSAGE_TYPE.into(), + payload, + } + } + + fn setup(buf: B) -> anyhow::Result> { + let mut r: Ref = buf.zk_zeroized()?; + r.init(); + Ok(r) + } + + fn init(&mut self) { + self.msg_type = Self::MESSAGE_TYPE.into(); + } +} + +#[repr(packed)] +#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)] +pub struct PingResponsePayload { + /// Randomly generated connection id + pub echo: [u8; 256], +} + +pub type PingResponse = ResponseEnvelope; + +impl PingResponse { + pub fn new(echo: [u8; 256]) -> Self { + Self::from_payload(PingResponsePayload { echo }) + } +} + +impl Message for PingResponse { + type Payload = PingResponsePayload; + type MessageClass = ResponseMsgType; + const MESSAGE_TYPE: Self::MessageClass = ResponseMsgType::Ping; + + fn from_payload(payload: Self::Payload) -> Self { + Self { + msg_type: Self::MESSAGE_TYPE.into(), + payload, + } + } + + fn setup(buf: B) -> anyhow::Result> { + let mut r: Ref = buf.zk_zeroized()?; + r.init(); + Ok(r) + } + + fn init(&mut self) { + self.msg_type = Self::MESSAGE_TYPE.into(); + } +} diff --git a/rosenpass/src/api/boilerplate/request_ref.rs b/rosenpass/src/api/boilerplate/request_ref.rs new file mode 100644 index 00000000..22f6bd1a --- /dev/null +++ b/rosenpass/src/api/boilerplate/request_ref.rs @@ -0,0 +1,107 @@ +use anyhow::ensure; + +use zerocopy::{ByteSlice, ByteSliceMut, Ref}; + +use super::{ByteSliceRefExt, MessageAttributes, PingRequest, RequestMsgType}; + +struct RequestRefMaker { + buf: B, + msg_type: RequestMsgType, +} + +impl RequestRef { + pub fn parse(buf: B) -> anyhow::Result { + RequestRefMaker::new(buf)?.parse() + } + + pub fn parse_from_prefix(buf: B) -> anyhow::Result { + RequestRefMaker::new(buf)?.from_prefix()?.parse() + } + + pub fn parse_from_suffix(buf: B) -> anyhow::Result { + RequestRefMaker::new(buf)?.from_suffix()?.parse() + } + + pub fn message_type(&self) -> RequestMsgType { + match self { + Self::Ping(_) => RequestMsgType::Ping, + } + } +} + +impl From> for RequestRef { + fn from(v: Ref) -> Self { + Self::Ping(v) + } +} + +impl RequestRefMaker { + fn new(buf: B) -> anyhow::Result { + let msg_type = buf.deref().request_msg_type_from_prefix()?; + Ok(Self { buf, msg_type }) + } + + fn target_size(&self) -> usize { + self.msg_type.message_size() + } + + fn parse(self) -> anyhow::Result> { + Ok(match self.msg_type { + RequestMsgType::Ping => RequestRef::Ping(self.buf.ping_request()?), + }) + } + + #[allow(clippy::wrong_self_convention)] + fn from_prefix(self) -> anyhow::Result { + self.ensure_fit()?; + let point = self.target_size(); + let Self { buf, msg_type } = self; + let (buf, _) = buf.split_at(point); + Ok(Self { buf, msg_type }) + } + + #[allow(clippy::wrong_self_convention)] + fn from_suffix(self) -> anyhow::Result { + self.ensure_fit()?; + let point = self.buf.len() - self.target_size(); + let Self { buf, msg_type } = self; + let (buf, _) = buf.split_at(point); + Ok(Self { buf, msg_type }) + } + + pub fn ensure_fit(&self) -> anyhow::Result<()> { + let have = self.buf.len(); + let need = self.target_size(); + ensure!( + need <= have, + "Buffer is undersized at {have} bytes (need {need} bytes)!" + ); + Ok(()) + } +} + +pub enum RequestRef { + Ping(Ref), +} + +impl RequestRef +where + B: ByteSlice, +{ + pub fn bytes(&self) -> &[u8] { + match self { + Self::Ping(r) => r.bytes(), + } + } +} + +impl RequestRef +where + B: ByteSliceMut, +{ + pub fn bytes_mut(&mut self) -> &[u8] { + match self { + Self::Ping(r) => r.bytes_mut(), + } + } +} diff --git a/rosenpass/src/api/boilerplate/request_response.rs b/rosenpass/src/api/boilerplate/request_response.rs new file mode 100644 index 00000000..5af7a205 --- /dev/null +++ b/rosenpass/src/api/boilerplate/request_response.rs @@ -0,0 +1,103 @@ +use rosenpass_util::zerocopy::{ + RefMaker, ZerocopyEmancipateExt, ZerocopyEmancipateMutExt, ZerocopySliceExt, +}; +use zerocopy::{ByteSlice, ByteSliceMut, Ref}; + +use super::{Message, PingRequest, PingResponse}; +use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt}; + +pub trait RequestMsg: Sized + Message { + type ResponseMsg: ResponseMsg; + + fn zk_response_maker(buf: B) -> RefMaker { + buf.zk_ref_maker() + } + + fn setup_response(buf: B) -> anyhow::Result> { + Self::zk_response_maker(buf).setup_msg() + } + + fn setup_response_from_prefix( + buf: B, + ) -> anyhow::Result> { + Self::zk_response_maker(buf).from_prefix()?.setup_msg() + } + + fn setup_response_from_suffix( + buf: B, + ) -> anyhow::Result> { + Self::zk_response_maker(buf).from_prefix()?.setup_msg() + } +} + +pub trait ResponseMsg: Message { + type RequestMsg: RequestMsg; +} + +impl RequestMsg for PingRequest { + type ResponseMsg = PingResponse; +} + +impl ResponseMsg for PingResponse { + type RequestMsg = PingRequest; +} + +pub type PingPair = (Ref, Ref); + +pub enum RequestResponsePair { + Ping(PingPair), +} + +impl From> for RequestResponsePair { + fn from(v: PingPair) -> Self { + RequestResponsePair::Ping(v) + } +} + +impl RequestResponsePair +where + B1: ByteSlice, + B2: ByteSlice, +{ + pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) { + match self { + Self::Ping((req, res)) => { + let req = RequestRef::Ping(req.emancipate()); + let res = ResponseRef::Ping(res.emancipate()); + (req, res) + } + } + } + + pub fn request(&self) -> RequestRef<&[u8]> { + self.both().0 + } + + pub fn response(&self) -> ResponseRef<&[u8]> { + self.both().1 + } +} + +impl RequestResponsePair +where + B1: ByteSliceMut, + B2: ByteSliceMut, +{ + pub fn both_mut(&mut self) -> (RequestRef<&mut [u8]>, ResponseRef<&mut [u8]>) { + match self { + Self::Ping((req, res)) => { + let req = RequestRef::Ping(req.emancipate_mut()); + let res = ResponseRef::Ping(res.emancipate_mut()); + (req, res) + } + } + } + + pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> { + self.both_mut().0 + } + + pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> { + self.both_mut().1 + } +} diff --git a/rosenpass/src/api/boilerplate/response_ref.rs b/rosenpass/src/api/boilerplate/response_ref.rs new file mode 100644 index 00000000..38b1e751 --- /dev/null +++ b/rosenpass/src/api/boilerplate/response_ref.rs @@ -0,0 +1,108 @@ +// TODO: This is copied verbatim from ResponseRef…not pretty +use anyhow::ensure; + +use zerocopy::{ByteSlice, ByteSliceMut, Ref}; + +use super::{ByteSliceRefExt, MessageAttributes, PingResponse, ResponseMsgType}; + +struct ResponseRefMaker { + buf: B, + msg_type: ResponseMsgType, +} + +impl ResponseRef { + pub fn parse(buf: B) -> anyhow::Result { + ResponseRefMaker::new(buf)?.parse() + } + + pub fn parse_from_prefix(buf: B) -> anyhow::Result { + ResponseRefMaker::new(buf)?.from_prefix()?.parse() + } + + pub fn parse_from_suffix(buf: B) -> anyhow::Result { + ResponseRefMaker::new(buf)?.from_suffix()?.parse() + } + + pub fn message_type(&self) -> ResponseMsgType { + match self { + Self::Ping(_) => ResponseMsgType::Ping, + } + } +} + +impl From> for ResponseRef { + fn from(v: Ref) -> Self { + Self::Ping(v) + } +} + +impl ResponseRefMaker { + fn new(buf: B) -> anyhow::Result { + let msg_type = buf.deref().response_msg_type_from_prefix()?; + Ok(Self { buf, msg_type }) + } + + fn target_size(&self) -> usize { + self.msg_type.message_size() + } + + fn parse(self) -> anyhow::Result> { + Ok(match self.msg_type { + ResponseMsgType::Ping => ResponseRef::Ping(self.buf.ping_response()?), + }) + } + + #[allow(clippy::wrong_self_convention)] + fn from_prefix(self) -> anyhow::Result { + self.ensure_fit()?; + let point = self.target_size(); + let Self { buf, msg_type } = self; + let (buf, _) = buf.split_at(point); + Ok(Self { buf, msg_type }) + } + + #[allow(clippy::wrong_self_convention)] + fn from_suffix(self) -> anyhow::Result { + self.ensure_fit()?; + let point = self.buf.len() - self.target_size(); + let Self { buf, msg_type } = self; + let (buf, _) = buf.split_at(point); + Ok(Self { buf, msg_type }) + } + + pub fn ensure_fit(&self) -> anyhow::Result<()> { + let have = self.buf.len(); + let need = self.target_size(); + ensure!( + need <= have, + "Buffer is undersized at {have} bytes (need {need} bytes)!" + ); + Ok(()) + } +} + +pub enum ResponseRef { + Ping(Ref), +} + +impl ResponseRef +where + B: ByteSlice, +{ + pub fn bytes(&self) -> &[u8] { + match self { + Self::Ping(r) => r.bytes(), + } + } +} + +impl ResponseRef +where + B: ByteSliceMut, +{ + pub fn bytes_mut(&mut self) -> &[u8] { + match self { + Self::Ping(r) => r.bytes_mut(), + } + } +} diff --git a/rosenpass/src/api/boilerplate/server.rs b/rosenpass/src/api/boilerplate/server.rs new file mode 100644 index 00000000..d4e42db9 --- /dev/null +++ b/rosenpass/src/api/boilerplate/server.rs @@ -0,0 +1,40 @@ +use zerocopy::{ByteSlice, ByteSliceMut}; + +use super::{ByteSliceRefExt, Message, PingRequest, PingResponse, RequestRef, RequestResponsePair}; + +pub trait Server { + fn ping(&mut self, req: &PingRequest, res: &mut PingResponse) -> anyhow::Result<()>; + + fn dispatch( + &mut self, + p: &mut RequestResponsePair, + ) -> anyhow::Result<()> + where + ReqBuf: ByteSlice, + ResBuf: ByteSliceMut, + { + match p { + RequestResponsePair::Ping((req, res)) => self.ping(req, res), + } + } + + fn handle_message(&mut self, req: ReqBuf, res: ResBuf) -> anyhow::Result + where + ReqBuf: ByteSlice, + ResBuf: ByteSliceMut, + { + let req = req.parse_request_from_prefix()?; + // TODO: This is not pretty; This match should be moved into RequestRef + let mut pair = match req { + RequestRef::Ping(req) => { + let mut res = res.ping_response_from_prefix()?; + res.init(); + RequestResponsePair::Ping((req, res)) + } + }; + self.dispatch(&mut pair)?; + + let res_len = pair.request().bytes().len(); + Ok(res_len) + } +} diff --git a/rosenpass/src/api/cli.rs b/rosenpass/src/api/cli.rs new file mode 100644 index 00000000..6c45c896 --- /dev/null +++ b/rosenpass/src/api/cli.rs @@ -0,0 +1,40 @@ +use std::path::PathBuf; + +use clap::Args; + +use crate::config::Rosenpass as RosenpassConfig; + +use super::config::ApiConfig; + +#[cfg(feature = "experiment_api")] +#[derive(Args, Debug)] +pub struct ApiCli { + /// Where in the file-system to create the unix socket the rosenpass API will be listening for + /// connections on. + #[arg(long)] + api_listen_path: Vec, + + /// When rosenpass is called from another process, the other process can open and bind the + /// unix socket for the Rosenpass API to use themselves, passing it to this process. In Rust this can be achieved + /// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate. + #[arg(long)] + api_listen_fd: Vec, + + /// When rosenpass is called from another process, the other process can connect the unix socket for the API + /// themselves, for instance using the `socketpair(2)` system call. + #[arg(long)] + api_stream_fd: Vec, +} + +impl ApiCli { + pub fn apply_to_config(&self, cfg: &mut RosenpassConfig) -> anyhow::Result<()> { + self.apply_to_api_config(&mut cfg.api) + } + + pub fn apply_to_api_config(&self, cfg: &mut ApiConfig) -> anyhow::Result<()> { + cfg.listen_path.extend_from_slice(&self.api_listen_path); + cfg.listen_fd.extend_from_slice(&self.api_listen_fd); + cfg.stream_fd.extend_from_slice(&self.api_stream_fd); + Ok(()) + } +} diff --git a/rosenpass/src/api/config.rs b/rosenpass/src/api/config.rs new file mode 100644 index 00000000..0a410a4d --- /dev/null +++ b/rosenpass/src/api/config.rs @@ -0,0 +1,41 @@ +use std::path::PathBuf; + +use mio::net::UnixListener; +use rosenpass_util::mio::{UnixListenerExt, UnixStreamExt}; +use serde::{Deserialize, Serialize}; + +use crate::app_server::AppServer; + +#[derive(Debug, Serialize, Deserialize, Default, Clone)] +pub struct ApiConfig { + /// Where in the file-system to create the unix socket the rosenpass API will be listening for + /// connections on + pub listen_path: Vec, + + /// When rosenpass is called from another process, the other process can open and bind the + /// unix socket for the Rosenpass API to use themselves, passing it to this process. In Rust this can be achieved + /// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate. + pub listen_fd: Vec, + + /// When rosenpass is called from another process, the other process can connect the unix socket for the API + /// themselves, for instance using the `socketpair(2)` system call. + pub stream_fd: Vec, +} + +impl ApiConfig { + pub fn apply_to_app_server(&self, srv: &mut AppServer) -> anyhow::Result<()> { + for path in self.listen_path.iter() { + srv.add_api_listener(UnixListener::bind(path)?)?; + } + + for fd in self.listen_fd.iter() { + srv.add_api_listener(UnixListenerExt::claim_fd(*fd)?)?; + } + + for fd in self.stream_fd.iter() { + srv.add_api_connection(UnixStreamExt::claim_fd(*fd)?)?; + } + + Ok(()) + } +} diff --git a/rosenpass/src/api/crypto_server_api_handler.rs b/rosenpass/src/api/crypto_server_api_handler.rs new file mode 100644 index 00000000..003f1d86 --- /dev/null +++ b/rosenpass/src/api/crypto_server_api_handler.rs @@ -0,0 +1,44 @@ +use rosenpass_to::{ops::copy_slice, To}; + +use crate::protocol::CryptoServer; + +use super::Server as ApiServer; + +#[derive(Debug)] +pub struct CryptoServerApiState { + _dummy: (), +} + +impl CryptoServerApiState { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { _dummy: () } + } + + pub fn acquire_backend<'a>( + &'a mut self, + crypto: &'a mut Option, + ) -> CryptoServerApiHandler<'a> { + let state = self; + CryptoServerApiHandler { state, crypto } + } +} + +pub struct CryptoServerApiHandler<'a> { + #[allow(unused)] // TODO: Remove + crypto: &'a mut Option, + #[allow(unused)] // TODO: Remove + state: &'a mut CryptoServerApiState, +} + +impl<'a> ApiServer for CryptoServerApiHandler<'a> { + fn ping( + &mut self, + req: &super::PingRequest, + res: &mut super::PingResponse, + ) -> anyhow::Result<()> { + let (req, res) = (&req.payload, &mut res.payload); + copy_slice(&req.echo).to(&mut res.echo); + Ok(()) + } +} diff --git a/rosenpass/src/api/mio/connection.rs b/rosenpass/src/api/mio/connection.rs new file mode 100644 index 00000000..e2d196a4 --- /dev/null +++ b/rosenpass/src/api/mio/connection.rs @@ -0,0 +1,167 @@ +use mio::{net::UnixStream, Interest}; +use rosenpass_util::{ + io::{IoResultKindHintExt, TryIoResultKindHintExt}, + length_prefix_encoding::{ + decoder::{self as lpe_decoder, LengthPrefixDecoder}, + encoder::{self as lpe_encoder, LengthPrefixEncoder}, + }, +}; +use zeroize::Zeroize; + +use crate::{api::Server, app_server::MioTokenDispenser, protocol::CryptoServer}; + +use super::super::{CryptoServerApiState, MAX_REQUEST_LEN, MAX_RESPONSE_LEN}; + +#[derive(Debug)] +pub struct MioConnection { + io: UnixStream, + invalid_read: bool, + read_buffer: LengthPrefixDecoder<[u8; MAX_REQUEST_LEN]>, + write_buffer: LengthPrefixEncoder<[u8; MAX_RESPONSE_LEN]>, + api_state: CryptoServerApiState, +} + +impl MioConnection { + pub fn new( + mut io: UnixStream, + registry: &mio::Registry, + token_dispenser: &mut MioTokenDispenser, // TODO: We should actually start using tokens… + ) -> std::io::Result { + registry.register( + &mut io, + token_dispenser.dispense(), + Interest::READABLE | Interest::WRITABLE, + )?; + + let invalid_read = false; + let read_buffer = LengthPrefixDecoder::new([0u8; MAX_REQUEST_LEN]); + let write_buffer = LengthPrefixEncoder::from_buffer([0u8; MAX_RESPONSE_LEN]); + let api_state = CryptoServerApiState::new(); + Ok(Self { + io, + invalid_read, + read_buffer, + write_buffer, + api_state, + }) + } + + pub fn poll(&mut self, crypto: &mut Option) -> anyhow::Result<()> { + self.flush_write_buffer()?; + if self.write_buffer.exhausted() { + self.recv(crypto)?; + } + Ok(()) + } + + // This is *exclusively* called by recv if the read_buffer holds a message + fn handle_incoming_message(&mut self, crypto: &mut Option) -> anyhow::Result<()> { + // Unwrap is allowed because recv() confirms before the call that a message was + // received + let req = self.read_buffer.message().unwrap().unwrap(); + + // TODO: The API should not return anyhow::Result + let response_len = self + .api_state + .acquire_backend(crypto) + .handle_message(req, self.write_buffer.buffer_bytes_mut())?; + self.read_buffer.zeroize(); // clear for new message to read + self.write_buffer + .restart_write_with_new_message(response_len)?; + + self.flush_write_buffer()?; + Ok(()) + } + + fn flush_write_buffer(&mut self) -> anyhow::Result<()> { + if self.write_buffer.exhausted() { + return Ok(()); + } + + loop { + use lpe_encoder::WriteToIoReturn as Ret; + use std::io::ErrorKind as K; + + match self + .write_buffer + .write_to_stdio(&self.io) + .io_err_kind_hint() + { + // Done + Ok(Ret { done: true, .. }) => { + self.write_buffer.zeroize(); // clear for new message to write + break; + } + + // Would block + Ok(Ret { + bytes_written: 0, .. + }) => break, + Err((_e, K::WouldBlock)) => break, + + // Just continue + Ok(_) => continue, /* Ret { bytes_written > 0, done = false } acc. to previous cases*/ + Err((_e, K::Interrupted)) => continue, + + // Other errors + Err((e, _ek)) => Err(e)?, + } + } + + Ok(()) + } + + fn recv(&mut self, crypto: &mut Option) -> anyhow::Result<()> { + if !self.write_buffer.exhausted() || self.invalid_read { + return Ok(()); + } + + loop { + use lpe_decoder::{ReadFromIoError as E, ReadFromIoReturn as Ret}; + use std::io::ErrorKind as K; + + match self + .read_buffer + .read_from_stdio(&self.io) + .try_io_err_kind_hint() + { + // We actually received a proper message + // (Impl below match to appease borrow checker) + Ok(Ret { + message: Some(_msg), + .. + }) => {} + + // Message does not fit in buffer + Err((e @ E::MessageTooLargeError { .. }, _)) => { + log::warn!("Received message on API that was too big to fit in our buffers; \ + looks like the client is broken. Stopping to process messages of the client.\n\ + Error: {e:?}"); + // TODO: We should properly close down the socket in this case, but to do that, + // we need to have the facilities in the Rosenpass IO handling system to close + // open connections. + // Just leaving the API connections dangling for now. + // This should be fixed for non-experimental use of the API. + self.invalid_read = true; + break; + } + + // Would block + Ok(Ret { bytes_read: 0, .. }) => break, + Err((_, Some(K::WouldBlock))) => break, + + // Just keep going + Ok(Ret { bytes_read: _, .. }) => continue, + Err((_, Some(K::Interrupted))) => continue, + + // Other IO Error (just pass on to the caller) + Err((E::IoError(e), _)) => Err(e)?, + }; + + self.handle_incoming_message(crypto)?; + break; // Handle just one message, leave some room for other IO handlers + } + + Ok(()) + } +} diff --git a/rosenpass/src/api/mio/manager.rs b/rosenpass/src/api/mio/manager.rs new file mode 100644 index 00000000..c1ed019d --- /dev/null +++ b/rosenpass/src/api/mio/manager.rs @@ -0,0 +1,93 @@ +use std::io; + +use mio::net::{UnixListener, UnixStream}; + +use rosenpass_util::{io::nonblocking_handle_io_errors, mio::interest::RW as MIO_RW}; + +use crate::{app_server::MioTokenDispenser, protocol::CryptoServer}; + +use super::MioConnection; + +#[derive(Default, Debug)] +pub struct MioManager { + listeners: Vec, + connections: Vec, +} + +impl MioManager { + pub fn new() -> Self { + Self::default() + } + + pub fn add_listener( + &mut self, + mut listener: UnixListener, + registry: &mio::Registry, + token_dispenser: &mut MioTokenDispenser, + ) -> io::Result<()> { + registry.register(&mut listener, token_dispenser.dispense(), MIO_RW)?; + self.listeners.push(listener); + Ok(()) + } + + pub fn add_connection( + &mut self, + connection: UnixStream, + registry: &mio::Registry, + token_dispenser: &mut MioTokenDispenser, + ) -> io::Result<()> { + let connection = MioConnection::new(connection, registry, token_dispenser)?; + self.connections.push(connection); + Ok(()) + } + + pub fn poll( + &mut self, + crypto: &mut Option, + registry: &mio::Registry, + token_dispenser: &mut MioTokenDispenser, + ) -> anyhow::Result<()> { + self.accept_connections(registry, token_dispenser)?; + self.poll_connections(crypto)?; + Ok(()) + } + + fn accept_connections( + &mut self, + registry: &mio::Registry, + token_dispenser: &mut MioTokenDispenser, + ) -> io::Result<()> { + for idx in 0..self.listeners.len() { + self.accept_from(idx, registry, token_dispenser)?; + } + Ok(()) + } + + fn accept_from( + &mut self, + idx: usize, + registry: &mio::Registry, + token_dispenser: &mut MioTokenDispenser, + ) -> io::Result<()> { + // Accept connection until the socket would block or returns another error + // TODO: This currently only adds connections--we eventually need the ability to remove + // them as well, see the note in connection.rs + loop { + match nonblocking_handle_io_errors(|| self.listeners[idx].accept())? { + None => break, + Some((conn, _addr)) => { + self.add_connection(conn, registry, token_dispenser)?; + } + }; + } + + Ok(()) + } + + fn poll_connections(&mut self, crypto: &mut Option) -> anyhow::Result<()> { + for conn in self.connections.iter_mut() { + conn.poll(crypto)? + } + Ok(()) + } +} diff --git a/rosenpass/src/api/mio/mod.rs b/rosenpass/src/api/mio/mod.rs new file mode 100644 index 00000000..c284c8b3 --- /dev/null +++ b/rosenpass/src/api/mio/mod.rs @@ -0,0 +1,5 @@ +mod connection; +mod manager; + +pub use connection::*; +pub use manager::*; diff --git a/rosenpass/src/api/mod.rs b/rosenpass/src/api/mod.rs new file mode 100644 index 00000000..9c9781dc --- /dev/null +++ b/rosenpass/src/api/mod.rs @@ -0,0 +1,9 @@ +mod boilerplate; +mod crypto_server_api_handler; + +pub use boilerplate::*; +pub use crypto_server_api_handler::*; + +pub mod cli; +pub mod config; +pub mod mio; diff --git a/rosenpass/src/app_server.rs b/rosenpass/src/app_server.rs index b6d34c42..15353bc7 100644 --- a/rosenpass/src/app_server.rs +++ b/rosenpass/src/app_server.rs @@ -1,5 +1,6 @@ use anyhow::bail; +use anyhow::Context; use anyhow::Result; use derive_builder::Builder; use log::{error, info, warn}; @@ -16,7 +17,9 @@ use std::cell::Cell; use std::collections::HashMap; use std::fmt::Debug; +use std::io::stdout; use std::io::ErrorKind; +use std::io::Write; use std::net::Ipv4Addr; use std::net::Ipv6Addr; use std::net::SocketAddr; @@ -63,7 +66,7 @@ pub struct MioTokenDispenser { } impl MioTokenDispenser { - fn dispense(&mut self) -> Token { + pub fn dispense(&mut self) -> Token { let r = self.counter; self.counter += 1; Token(r) @@ -144,7 +147,7 @@ pub struct AppServerTest { // TODO add user control via unix domain socket and stdin/stdout #[derive(Debug)] pub struct AppServer { - pub crypt: CryptoServer, + pub crypt: Option, pub sockets: Vec, pub events: mio::Events, pub mio_poll: mio::Poll, @@ -159,6 +162,8 @@ pub struct AppServer { pub unpolled_count: usize, pub last_update_time: Instant, pub test_helpers: Option, + #[cfg(feature = "experiment_api")] + pub api_manager: crate::api::mio::MioManager, } /// A socket pointer is an index assigned to a socket; @@ -601,7 +606,7 @@ impl AppServer { // TODO use mio::net::UnixStream together with std::os::unix::net::UnixStream for Linux Ok(Self { - crypt: CryptoServer::new(sk, pk), + crypt: Some(CryptoServer::new(sk, pk)), peers: Vec::new(), verbosity, sockets, @@ -616,9 +621,23 @@ impl AppServer { unpolled_count: 0, last_update_time: Instant::now(), test_helpers, + #[cfg(feature = "experiment_api")] + api_manager: crate::api::mio::MioManager::default(), }) } + pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> { + self.crypt + .as_ref() + .context("Cryptography handler not initialized") + } + + pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> { + self.crypt + .as_mut() + .context("Cryptography handler not initialized") + } + pub fn verbose(&self) -> bool { matches!(self.verbosity, Verbosity::Verbose) } @@ -669,7 +688,7 @@ impl AppServer { broker_peer: Option, hostname: Option, ) -> anyhow::Result { - let PeerPtr(pn) = self.crypt.add_peer(psk, pk)?; + let PeerPtr(pn) = self.crypto_server_mut()?.add_peer(psk, pk)?; assert!(pn == self.peers.len()); let initial_endpoint = hostname .map(Endpoint::discovery_from_hostname) @@ -712,7 +731,7 @@ impl AppServer { ); if tries_left > 0 { error!("re-initializing networking in {sleep}! {tries_left} tries left."); - std::thread::sleep(self.crypt.timebase.dur(sleep)); + std::thread::sleep(self.crypto_server_mut()?.timebase.dur(sleep)); continue; } @@ -758,11 +777,11 @@ impl AppServer { match self.poll(&mut *rx)? { #[allow(clippy::redundant_closure_call)] SendInitiation(peer) => tx_maybe_with!(peer, || self - .crypt + .crypto_server_mut()? .initiate_handshake(peer.lower(), &mut *tx))?, #[allow(clippy::redundant_closure_call)] SendRetransmission(peer) => tx_maybe_with!(peer, || self - .crypt + .crypto_server_mut()? .retransmit_handshake(peer.lower(), &mut *tx))?, DeleteKey(peer) => { self.output_key(peer, Stale, &SymKey::random())?; @@ -783,7 +802,9 @@ impl AppServer { DoSOperation::UnderLoad => { self.handle_msg_under_load(&endpoint, &rx[..len], &mut *tx) } - DoSOperation::Normal => self.crypt.handle_msg(&rx[..len], &mut *tx), + DoSOperation::Normal => { + self.crypto_server_mut()?.handle_msg(&rx[..len], &mut *tx) + } }; match msg_result { Err(ref e) => { @@ -811,7 +832,8 @@ impl AppServer { ap.get_app_mut(self).current_endpoint = Some(endpoint); // TODO: Maybe we should rather call the key "rosenpass output"? - self.output_key(ap, Exchanged, &self.crypt.osk(p)?)?; + let osk = &self.crypto_server_mut()?.osk(p)?; + self.output_key(ap, Exchanged, osk)?; } } } @@ -827,9 +849,9 @@ impl AppServer { tx: &mut [u8], ) -> Result { match endpoint { - Endpoint::SocketBoundAddress(socket) => { - self.crypt.handle_msg_under_load(rx, &mut *tx, socket) - } + Endpoint::SocketBoundAddress(socket) => self + .crypto_server_mut()? + .handle_msg_under_load(rx, &mut *tx, socket), Endpoint::Discovery(_) => { anyhow::bail!("Host-path discovery is not supported under load") } @@ -842,7 +864,7 @@ impl AppServer { why: KeyOutputReason, key: &SymKey, ) -> anyhow::Result<()> { - let peerid = peer.lower().get(&self.crypt).pidt()?; + let peerid = peer.lower().get(self.crypto_server()?).pidt()?; if self.verbose() { let msg = match why { @@ -870,10 +892,14 @@ impl AppServer { // this is intentionally writing to stdout instead of stderr, because // it is meant to allow external detection of a successful key-exchange - println!( + let stdout = stdout(); + let mut stdout = stdout.lock(); + writeln!( + stdout, "output-key peer {} key-file {of:?} {why}", peerid.fmt_b64::() - ); + )?; + stdout.flush()?; } peer.set_psk(self, key)?; @@ -885,7 +911,7 @@ impl AppServer { use crate::protocol::PollResult as C; use AppPollResult as A; loop { - return Ok(match self.crypt.poll()? { + return Ok(match self.crypto_server_mut()?.poll()? { C::DeleteKey(PeerPtr(no)) => A::DeleteKey(AppPeerPtr(no)), C::SendInitiation(PeerPtr(no)) => A::SendInitiation(AppPeerPtr(no)), C::SendRetransmission(PeerPtr(no)) => A::SendRetransmission(AppPeerPtr(no)), @@ -1013,6 +1039,33 @@ impl AppServer { broker.process_poll()?; } + // API poll + + #[cfg(feature = "experiment_api")] + self.api_manager.poll( + &mut self.crypt, + self.mio_poll.registry(), + &mut self.mio_token_dispenser, + )?; + Ok(None) } + + #[cfg(feature = "experiment_api")] + pub fn add_api_connection(&mut self, connection: mio::net::UnixStream) -> std::io::Result<()> { + self.api_manager.add_connection( + connection, + self.mio_poll.registry(), + &mut self.mio_token_dispenser, + ) + } + + #[cfg(feature = "experiment_api")] + pub fn add_api_listener(&mut self, listener: mio::net::UnixListener) -> std::io::Result<()> { + self.api_manager.add_listener( + listener, + self.mio_poll.registry(), + &mut self.mio_token_dispenser, + ) + } } diff --git a/rosenpass/src/bin/gen-ipc-msg-types.rs b/rosenpass/src/bin/gen-ipc-msg-types.rs new file mode 100644 index 00000000..2e128de6 --- /dev/null +++ b/rosenpass/src/bin/gen-ipc-msg-types.rs @@ -0,0 +1,86 @@ +use anyhow::{Context, Result}; +use heck::ToShoutySnakeCase; + +use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN}; + +fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]> { + match values.split_first() { + Some((head, tail)) => calculate_hash_value(hd.mix(head.as_bytes())?, tail), + None => Ok(hd.into_value()), + } +} + +fn print_literal(path: &[&str]) -> Result<()> { + let val = calculate_hash_value(HashDomain::zero(), path)?; + let (last, prefix) = path.split_last().context("developer error!")?; + let var_name = last.to_shouty_snake_case(); + + print!("// hash domain hash of: "); + for n in prefix.iter() { + print!("{n} -> "); + } + println!("{last}"); + + let c = hex::encode(val) + .chars() + .collect::>() + .chunks_exact(4) + .map(|chunk| chunk.iter().collect::()) + .collect::>(); + println!("const {var_name} : RawMsgType = RawMsgType::from_le_bytes(hex!(\"{} {} {} {} {} {} {} {}\"));", + c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]); + + Ok(()) +} + +#[derive(Debug, Clone)] +enum Tree { + Branch(String, Vec), + Leaf(String), +} + +impl Tree { + fn name(&self) -> &str { + match self { + Self::Branch(name, _) => name, + Self::Leaf(name) => name, + } + } + + fn gen_code_inner(&self, prefix: &[&str]) -> Result<()> { + let mut path = prefix.to_owned(); + path.push(self.name()); + + match self { + Self::Branch(_, ref children) => { + for c in children.iter() { + c.gen_code_inner(&path)? + } + } + Self::Leaf(_) => print_literal(&path)?, + }; + + Ok(()) + } + + fn gen_code(&self) -> Result<()> { + self.gen_code_inner(&[]) + } +} + +fn main() -> Result<()> { + let tree = Tree::Branch( + "Rosenpass IPC API".to_owned(), + vec![Tree::Branch( + "Rosenpass Protocol Server".to_owned(), + vec![ + Tree::Leaf("Ping Request".to_owned()), + Tree::Leaf("Ping Response".to_owned()), + ], + )], + ); + + println!("type RawMsgType = u128;"); + println!(); + tree.gen_code() +} diff --git a/rosenpass/src/cli.rs b/rosenpass/src/cli.rs index d69d4046..fae2f88b 100644 --- a/rosenpass/src/cli.rs +++ b/rosenpass/src/cli.rs @@ -3,9 +3,6 @@ use clap::{Parser, Subcommand}; use rosenpass_cipher_traits::Kem; use rosenpass_ciphers::kem::StaticKem; use rosenpass_secret_memory::file::StoreSecret; -use rosenpass_secret_memory::{ - secret_policy_try_use_memfd_secrets, secret_policy_use_only_malloc_secrets, -}; use rosenpass_util::file::{LoadValue, LoadValueB64, StoreValue}; use rosenpass_wireguard_broker::brokers::native_unix::{ NativeUnixBroker, NativeUnixBrokerConfigBaseBuilder, NativeUnixBrokerConfigBaseBuilderError, @@ -35,11 +32,21 @@ pub struct CliArgs { #[arg(short, long, group = "log-level")] quiet: bool, + #[command(flatten)] + #[cfg(feature = "experiment_api")] + api: crate::api::cli::ApiCli, + #[command(subcommand)] pub command: CliCommand, } impl CliArgs { + pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> { + #[cfg(feature = "experiment_api")] + self.api.apply_to_config(_cfg)?; + Ok(()) + } + /// returns the log level filter set by CLI args /// returns `None` if the user did not specify any log level filter via CLI /// @@ -152,21 +159,14 @@ pub enum CliCommand { Man, } -impl CliCommand { +impl CliArgs { /// runs the command specified via CLI /// /// ## TODO /// - This method consumes the [`CliCommand`] value. It might be wise to use a reference... pub fn run(self, test_helpers: Option) -> anyhow::Result<()> { - //Specify secret policy - - #[cfg(feature = "enable_memfd_alloc")] - secret_policy_try_use_memfd_secrets(); - #[cfg(not(feature = "enable_memfd_alloc"))] - secret_policy_use_only_malloc_secrets(); - use CliCommand::*; - match self { + match &self.command { Man => { let man_cmd = std::process::Command::new("man") .args(["1", "rosenpass"]) @@ -178,7 +178,7 @@ impl CliCommand { } GenConfig { config_file, force } => { ensure!( - force || !config_file.exists(), + *force || !config_file.exists(), "config file {config_file:?} already exists" ); @@ -193,9 +193,9 @@ impl CliCommand { let mut secret_key: Option = None; // Manual arg parsing, since clap wants to prefix flags with "--" - let mut args = args.into_iter(); + let mut args = args.iter(); loop { - match (args.next().as_deref(), args.next()) { + match (args.next().map(|x| x.as_str()), args.next()) { (Some("private-key"), Some(opt)) | (Some("secret-key"), Some(opt)) => { secret_key = Some(opt.into()); } @@ -237,7 +237,7 @@ impl CliCommand { (config.public_key, config.secret_key) } - (_, Some(pkf), Some(skf)) => (pkf, skf), + (_, Some(pkf), Some(skf)) => (pkf.clone(), skf.clone()), _ => { bail!("either a config-file or both public-key and secret-key file are required") } @@ -269,31 +269,36 @@ impl CliCommand { "config file '{config_file:?}' does not exist" ); - let config = config::Rosenpass::load(config_file)?; + let mut config = config::Rosenpass::load(config_file)?; config.validate()?; + self.apply_to_config(&mut config)?; + Self::event_loop(config, test_helpers)?; } Exchange { first_arg, - mut rest_of_args, + rest_of_args, config_file, } => { - rest_of_args.insert(0, first_arg); + let mut rest_of_args = rest_of_args.clone(); + rest_of_args.insert(0, first_arg.clone()); let args = rest_of_args; let mut config = config::Rosenpass::parse_args(args)?; if let Some(p) = config_file { - config.store(&p)?; - config.config_file_path = p; + config.store(p)?; + config.config_file_path.clone_from(p); } config.validate()?; + self.apply_to_config(&mut config)?; + Self::event_loop(config, test_helpers)?; } Validate { config_files } => { for file in config_files { - match config::Rosenpass::load(&file) { + match config::Rosenpass::load(file) { Ok(config) => { eprintln!("{file:?} is valid TOML and conforms to the expected schema"); match config.validate() { @@ -315,6 +320,7 @@ impl CliCommand { test_helpers: Option, ) -> anyhow::Result<()> { const MAX_PSK_SIZE: usize = 1000; + // load own keys let sk = SSk::load(&config.secret_key)?; let pk = SPk::load(&config.public_key)?; @@ -323,11 +329,13 @@ impl CliCommand { let mut srv = std::boxed::Box::::new(AppServer::new( sk, pk, - config.listen, + config.listen.clone(), config.verbosity, test_helpers, )?); + config.apply_to_app_server(&mut srv)?; + let broker_store_ptr = srv.register_broker(Box::new(NativeUnixBroker::new()))?; fn cfg_err_map(e: NativeUnixBrokerConfigBaseBuilderError) -> anyhow::Error { @@ -375,3 +383,15 @@ fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow ssk.store_secret(secret_key)?; spk.store(public_key) } + +#[cfg(feature = "internal_testing")] +pub mod testing { + use super::*; + + pub fn generate_and_save_keypair( + secret_key: PathBuf, + public_key: PathBuf, + ) -> anyhow::Result<()> { + super::generate_and_save_keypair(secret_key, public_key) + } +} diff --git a/rosenpass/src/config.rs b/rosenpass/src/config.rs index ab320eef..e7538d35 100644 --- a/rosenpass/src/config.rs +++ b/rosenpass/src/config.rs @@ -19,6 +19,8 @@ use anyhow::{bail, ensure}; use rosenpass_util::file::{fopen_w, Visibility}; use serde::{Deserialize, Serialize}; +use crate::app_server::AppServer; + #[derive(Debug, Serialize, Deserialize)] pub struct Rosenpass { /// path to the public key file @@ -27,6 +29,10 @@ pub struct Rosenpass { /// path to the secret key file pub secret_key: PathBuf, + /// Location of the API listen sockets + #[cfg(feature = "experiment_api")] + pub api: crate::api::config::ApiConfig, + /// list of [`SocketAddr`] to listen on /// /// Examples: @@ -54,7 +60,7 @@ pub struct Rosenpass { /// ## TODO /// - replace this type with [`log::LevelFilter`], also see -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)] pub enum Verbosity { Quiet, Verbose, @@ -157,6 +163,12 @@ impl Rosenpass { self.store(&self.config_file_path) } + pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> { + #[cfg(feature = "experiment_api")] + self.api.apply_to_app_server(_srv)?; + Ok(()) + } + /// Validate a configuration /// /// ## TODO @@ -206,6 +218,8 @@ impl Rosenpass { public_key: PathBuf::from(public_key.as_ref()), secret_key: PathBuf::from(secret_key.as_ref()), listen: vec![], + #[cfg(feature = "experiment_api")] + api: crate::api::config::ApiConfig::default(), verbosity: Verbosity::Quiet, peers: vec![], config_file_path: PathBuf::new(), diff --git a/rosenpass/src/lib.rs b/rosenpass/src/lib.rs index a561dc04..b186bd21 100644 --- a/rosenpass/src/lib.rs +++ b/rosenpass/src/lib.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "experiment_api")] +pub mod api; pub mod app_server; pub mod cli; pub mod config; @@ -11,4 +13,8 @@ pub enum RosenpassError { BufferSizeMismatch, #[error("invalid message type")] InvalidMessageType(u8), + #[error("invalid API message type")] + InvalidApiMessageType(u128), + #[error("could not parse API message")] + InvalidApiMessage, } diff --git a/rosenpass/src/main.rs b/rosenpass/src/main.rs index acf28507..b769c48b 100644 --- a/rosenpass/src/main.rs +++ b/rosenpass/src/main.rs @@ -8,6 +8,14 @@ pub fn main() { // parse CLI arguments let args = CliArgs::parse(); + { + use rosenpass_secret_memory as SM; + #[cfg(feature = "experiment_memfd_secret")] + SM::secret_policy_try_use_memfd_secrets(); + #[cfg(not(feature = "experiment_memfd_secret"))] + SM::secret_policy_use_only_malloc_secrets(); + } + // init logging { let mut log_builder = env_logger::Builder::from_default_env(); // sets log level filter from environment (or defaults) @@ -26,10 +34,10 @@ pub fn main() { // error!("error dummy"); } - match args.command.run(None) { + match args.run(None) { Ok(_) => {} Err(e) => { - error!("{e}"); + error!("{e:?}"); exit(1); } } diff --git a/rosenpass/tests/api-integration-tests.rs b/rosenpass/tests/api-integration-tests.rs new file mode 100644 index 00000000..da9c84af --- /dev/null +++ b/rosenpass/tests/api-integration-tests.rs @@ -0,0 +1,180 @@ +use std::{ + io::{BufRead, BufReader}, + net::ToSocketAddrs, + os::unix::net::UnixStream, + process::Stdio, +}; + +use anyhow::{bail, Context}; +use rosenpass::api; +use rosenpass_to::{ops::copy_slice_least_src, To}; +use rosenpass_util::zerocopy::ZerocopySliceExt; +use rosenpass_util::{ + file::LoadValueB64, + length_prefix_encoding::{decoder::LengthPrefixDecoder, encoder::LengthPrefixEncoder}, +}; +use tempfile::TempDir; +use zerocopy::AsBytes; + +use rosenpass::protocol::SymKey; + +#[test] +fn api_integration_test() -> anyhow::Result<()> { + rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets(); + + let dir = TempDir::with_prefix("rosenpass-api-integration-test")?; + + macro_rules! tempfile { + ($($lst:expr),+) => {{ + let mut buf = dir.path().to_path_buf(); + $(buf.push($lst);)* + buf + }} + } + + let peer_a_endpoint = "[::1]:61423"; + let peer_a_osk = tempfile!("a.osk"); + let peer_b_osk = tempfile!("b.osk"); + + use rosenpass::config; + let peer_a = config::Rosenpass { + config_file_path: tempfile!("a.config"), + secret_key: tempfile!("a.sk"), + public_key: tempfile!("a.pk"), + listen: peer_a_endpoint.to_socket_addrs()?.collect(), // TODO: This could collide by accident + verbosity: config::Verbosity::Verbose, + api: api::config::ApiConfig { + listen_path: vec![tempfile!("a.sock")], + listen_fd: vec![], + stream_fd: vec![], + }, + peers: vec![config::RosenpassPeer { + public_key: tempfile!("b.pk"), + key_out: Some(peer_a_osk.clone()), + endpoint: None, + pre_shared_key: None, + wg: None, + }], + }; + + let peer_b = config::Rosenpass { + config_file_path: tempfile!("b.config"), + secret_key: tempfile!("b.sk"), + public_key: tempfile!("b.pk"), + listen: vec![], + verbosity: config::Verbosity::Verbose, + api: api::config::ApiConfig { + listen_path: vec![tempfile!("b.sock")], + listen_fd: vec![], + stream_fd: vec![], + }, + peers: vec![config::RosenpassPeer { + public_key: tempfile!("a.pk"), + key_out: Some(peer_b_osk.clone()), + endpoint: Some(peer_a_endpoint.to_owned()), + pre_shared_key: None, + wg: None, + }], + }; + + // Generate the keys + rosenpass::cli::testing::generate_and_save_keypair( + peer_a.secret_key.clone(), + peer_a.public_key.clone(), + )?; + rosenpass::cli::testing::generate_and_save_keypair( + peer_b.secret_key.clone(), + peer_b.public_key.clone(), + )?; + + // Write the configuration files + peer_a.commit()?; + peer_b.commit()?; + + // Start peer a + let proc_a = std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass")) + .args([ + "exchange-config", + peer_a.config_file_path.to_str().context("")?, + ]) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .spawn()?; + + // Start peer b + let proc_b = std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass")) + .args([ + "exchange-config", + peer_b.config_file_path.to_str().context("")?, + ]) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .spawn()?; + + // Acquire stdout + let mut out_a = BufReader::new(proc_a.stdout.context("")?).lines(); + let mut out_b = BufReader::new(proc_b.stdout.context("")?).lines(); + + // Wait for the keys to successfully exchange a key + let mut attempt = 0; + loop { + let line_a = out_a.next().context("")??; + let line_b = out_b.next().context("")??; + + let words_a = line_a.split(' ').collect::>(); + let words_b = line_b.split(' ').collect::>(); + + // FIXED FIXED PEER-ID FIXED FILENAME STATUS + // output-key peer KZqXTZ4l2aNnkJtLPhs4D8JxHTGmRSL9w3Qr+X8JxFk= key-file "client-A-osk" exchanged + let peer_a_id = words_b + .get(2) + .with_context(|| format!("Bad rosenpass output: `{line_b}`"))?; + let peer_b_id = words_a + .get(2) + .with_context(|| format!("Bad rosenpass output: `{line_a}`"))?; + assert_eq!( + line_a, + format!( + "output-key peer {peer_b_id} key-file \"{}\" exchanged", + peer_a_osk.to_str().context("")? + ) + ); + assert_eq!( + line_b, + format!( + "output-key peer {peer_a_id} key-file \"{}\" exchanged", + peer_b_osk.to_str().context("")? + ) + ); + + // Read OSKs + let osk_a = SymKey::load_b64::<64, _>(peer_a_osk.clone())?; + let osk_b = SymKey::load_b64::<64, _>(peer_b_osk.clone())?; + match osk_a.secret() == osk_b.secret() { + true => break, + false if attempt > 10 => bail!("Peers did not produce a matching key even after ten attempts. Something is wrong with the key exchange!"), + false => {}, + }; + + attempt += 1; + } + + // Now connect to the peers + let api_a = UnixStream::connect(&peer_a.api.listen_path[0])?; + let api_b = UnixStream::connect(&peer_b.api.listen_path[0])?; + + for conn in ([api_a, api_b]).iter() { + let mut echo = [0u8; 256]; + copy_slice_least_src("Hello World".as_bytes()).to(&mut echo); + + let req = api::PingRequest::new(echo); + LengthPrefixEncoder::from_message(req.as_bytes()).write_all_to_stdio(conn)?; + + let mut decoder = LengthPrefixDecoder::new([0u8; api::MAX_RESPONSE_LEN]); + let res = decoder.read_all_from_stdio(conn)?; + let res = res.zk_parse::()?; + assert_eq!(*res, api::PingResponse::new(echo)); + } + + Ok(()) +} diff --git a/rosenpass/tests/integration_test.rs b/rosenpass/tests/integration_test.rs index 66925d67..f572d68c 100644 --- a/rosenpass/tests/integration_test.rs +++ b/rosenpass/tests/integration_test.rs @@ -15,9 +15,19 @@ use std::io::Write; const BIN: &str = "rosenpass"; +fn setup_tests() { + use rosenpass_secret_memory as SM; + #[cfg(feature = "experiment_memfd_secret")] + SM::secret_policy_try_use_memfd_secrets(); + #[cfg(not(feature = "experiment_memfd_secret"))] + SM::secret_policy_use_only_malloc_secrets(); +} + // check that we can generate keys #[test] fn generate_keys() { + setup_tests(); + let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("keygen"); fs::create_dir_all(&tmpdir).unwrap(); @@ -94,14 +104,11 @@ fn run_server_client_exchange( .unwrap(); std::thread::spawn(move || { - cli.command - .run(Some( - server_test_builder - .termination_handler(Some(server_terminate_rx)) - .build() - .unwrap(), - )) + let test_helpers = server_test_builder + .termination_handler(Some(server_terminate_rx)) + .build() .unwrap(); + cli.run(Some(test_helpers)).unwrap(); }); let cli = CliArgs::try_parse_from( @@ -112,14 +119,11 @@ fn run_server_client_exchange( .unwrap(); std::thread::spawn(move || { - cli.command - .run(Some( - client_test_builder - .termination_handler(Some(client_terminate_rx)) - .build() - .unwrap(), - )) + let test_helpers = client_test_builder + .termination_handler(Some(client_terminate_rx)) + .build() .unwrap(); + cli.run(Some(test_helpers)).unwrap(); }); // give them some time to do the key exchange under load @@ -134,6 +138,7 @@ fn run_server_client_exchange( #[test] #[serial] fn check_exchange_under_normal() { + setup_tests(); setup_logging(); let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("exchange"); @@ -206,6 +211,7 @@ fn check_exchange_under_normal() { #[test] #[serial] fn check_exchange_under_dos() { + setup_tests(); setup_logging(); //Generate binary with responder with feature integration_test @@ -283,6 +289,7 @@ struct MockBrokerInner { interface: Option, } +#[allow(dead_code)] #[derive(Debug, Default)] struct MockBroker { inner: Arc>, @@ -321,7 +328,7 @@ impl rosenpass_wireguard_broker::WireGuardBroker for MockBroker { if let Ok(ref mut mutex) = lock { **mutex = MockBrokerInner { psk: Some(config.psk.clone()), - peer_id: Some(config.peer_id.clone()), + peer_id: Some(*config.peer_id), interface: Some(std::str::from_utf8(config.interface).unwrap().to_string()), }; break Ok(()); diff --git a/rp/Cargo.toml b/rp/Cargo.toml index 95e3babb..9166bdc9 100644 --- a/rp/Cargo.toml +++ b/rp/Cargo.toml @@ -39,5 +39,5 @@ tempfile = {workspace = true} stacker = {workspace = true} [features] -enable_memfd_alloc = [] +experiment_memfd_secret = [] experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"] diff --git a/rp/src/main.rs b/rp/src/main.rs index 014b0e77..7a2ab996 100644 --- a/rp/src/main.rs +++ b/rp/src/main.rs @@ -11,9 +11,9 @@ mod key; #[tokio::main] async fn main() { - #[cfg(feature = "enable_memfd_alloc")] + #[cfg(feature = "experiment_memfd_secret")] policy::secret_policy_try_use_memfd_secrets(); - #[cfg(not(feature = "enable_memfd_alloc"))] + #[cfg(not(feature = "experiment_memfd_secret"))] policy::secret_policy_use_only_malloc_secrets(); let cli = match Cli::parse(std::env::args().peekable()) { diff --git a/secret-memory/src/public.rs b/secret-memory/src/public.rs index 81fb6487..88bb785a 100644 --- a/secret-memory/src/public.rs +++ b/secret-memory/src/public.rs @@ -317,6 +317,7 @@ impl StoreValueB64Writer for PublicBox { mod tests { #[cfg(test)] + #[allow(clippy::module_inception)] mod tests { use crate::{Public, PublicBox}; use rosenpass_util::{ @@ -346,7 +347,7 @@ mod tests { // Store the original bytes to an example file in the temporary directory let example_file = temp_dir.path().join("example_file"); - std::fs::write(example_file.clone(), &original_bytes).unwrap(); + std::fs::write(&example_file, original_bytes).unwrap(); // Load the value from the example file into our generic type let loaded_public = T::load(&example_file).unwrap(); diff --git a/secret-memory/src/secret.rs b/secret-memory/src/secret.rs index 6173de38..9f4e50da 100644 --- a/secret-memory/src/secret.rs +++ b/secret-memory/src/secret.rs @@ -1,6 +1,5 @@ use std::cell::RefCell; use std::collections::HashMap; -use std::convert::TryInto; use std::fmt; use std::ops::{Deref, DerefMut}; use std::path::Path; @@ -387,7 +386,7 @@ mod test { // Store the original secret to an example file in the temporary directory let example_file = temp_dir.path().join("example_file"); - std::fs::write(example_file.clone(), &original_bytes).unwrap(); + std::fs::write(&example_file, original_bytes).unwrap(); // Load the secret from the example file let loaded_secret = Secret::load(&example_file).unwrap(); diff --git a/to/src/to/mod.rs b/to/src/to/mod.rs index 33f54d97..dfe4e522 100644 --- a/to/src/to/mod.rs +++ b/to/src/to/mod.rs @@ -6,8 +6,8 @@ //! - `Dst: ?Sized`; (e.g. [u8]) – The target to write to //! - `Out: Sized = &mut Dst`; (e.g. &mut [u8]) – A reference to the target to write to //! - `Coercable: ?Sized + DstCoercion`; (e.g. `[u8]`, `[u8; 16]`) – Some value that -//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of -//! `Dst` (e.g. `[u8; 64]`). +//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of +//! `Dst` (e.g. `[u8; 64]`). //! - `Ret: Sized`; (anything) – must be `CondenseBeside<_>` if condensing is to be applied. The ordinary return value of a function with an output //! - `Val: Sized + BorrowMut`; (e.g. [u8; 16]) – Some owned storage that can be borrowed as `Dst` //! - `Condensed: Sized = CondenseBeside::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>) – The combiation of Val and Ret after condensing was applied (`Beside::condense()`/`Ret::condense(v)` for all `v : Val`). diff --git a/util/Cargo.toml b/util/Cargo.toml index 7e2ef08b..e80ba0f5 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -18,3 +18,6 @@ typenum = { workspace = true } static_assertions = { workspace = true } rustix = {workspace = true} zeroize = {workspace = true} +zerocopy = { workspace = true } +thiserror = { workspace = true } +mio = { workspace = true } diff --git a/util/src/fd.rs b/util/src/fd.rs index cf5eaf95..95ff08e3 100644 --- a/util/src/fd.rs +++ b/util/src/fd.rs @@ -1,12 +1,50 @@ -use std::os::fd::{OwnedFd, RawFd}; +use rustix::{ + fd::{AsFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}, + io::{fcntl_dupfd_cloexec, DupFlags}, +}; -/// Clone some file descriptor +use crate::mem::Forgetting; + +/// Prepare a file descriptor for use in Rust code. /// -/// If the file descriptor is invalid, an error will be raised. -pub fn claim_fd(fd: RawFd) -> anyhow::Result { - use rustix::{fd::BorrowedFd, io::dup}; - - // This is safe since [dup] will simply raise - let fd = unsafe { dup(BorrowedFd::borrow_raw(fd))? }; - Ok(fd) +/// Checks if the file descriptor is valid and duplicates it to a new file descriptor. +/// The old file descriptor is masked to avoid potential use after free (on file descriptor) +/// in case the given file descriptor is still used somewhere +pub fn claim_fd(fd: RawFd) -> rustix::io::Result { + let new = clone_fd_cloexec(unsafe { BorrowedFd::borrow_raw(fd) })?; + mask_fd(fd)?; + Ok(new) +} + +pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> { + // Safety: because the OwnedFd resulting from OwnedFd::from_raw_fd is wrapped in a Forgetting, + // it never gets dropped, meaning that fd is never closed and thus outlives the OwnedFd + let mut owned = Forgetting::new(unsafe { OwnedFd::from_raw_fd(fd) }); + clone_fd_to_cloexec(open_nullfd()?, &mut owned) +} + +pub fn clone_fd_cloexec(fd: Fd) -> rustix::io::Result { + const MINFD: RawFd = 3; // Avoid stdin, stdout, and stderr + fcntl_dupfd_cloexec(fd, MINFD) +} + +#[cfg(target_os = "linux")] +pub fn clone_fd_to_cloexec(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> { + use rustix::io::dup3; + dup3(fd, new, DupFlags::CLOEXEC) +} + +#[cfg(not(target_os = "linux"))] +pub fn clone_fd_to_cloexec(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> { + use rustix::io::{dup2, fcntl_setfd, FdFlags}; + dup2(&fd, new)?; + fcntl_setfd(&new, FdFlags::CLOEXEC) +} + +/// Open a "blocked" file descriptor. I.e. a file descriptor that is neither meant for reading nor +/// writing +pub fn open_nullfd() -> rustix::io::Result { + use rustix::fs::{open, Mode, OFlags}; + // TODO: Add tests showing that this will throw errors on use + open("/dev/null", OFlags::CLOEXEC, Mode::empty()) } diff --git a/util/src/file.rs b/util/src/file.rs index af10ae04..dfba76c0 100644 --- a/util/src/file.rs +++ b/util/src/file.rs @@ -2,7 +2,6 @@ use anyhow::ensure; use std::fs::File; use std::io::Read; use std::os::unix::fs::OpenOptionsExt; -use std::result::Result; use std::{fs::OpenOptions, path::Path}; pub enum Visibility { diff --git a/util/src/functional.rs b/util/src/functional.rs index c138a2ef..10c5f945 100644 --- a/util/src/functional.rs +++ b/util/src/functional.rs @@ -13,3 +13,7 @@ where f(&v); v } + +pub fn run R>(f: F) -> R { + f() +} diff --git a/util/src/io.rs b/util/src/io.rs new file mode 100644 index 00000000..111959f1 --- /dev/null +++ b/util/src/io.rs @@ -0,0 +1,110 @@ +use std::{borrow::Borrow, io}; + +pub trait IoErrorKind { + fn io_error_kind(&self) -> io::ErrorKind; +} + +impl> IoErrorKind for T { + fn io_error_kind(&self) -> io::ErrorKind { + self.borrow().kind() + } +} + +pub trait TryIoErrorKind { + fn try_io_error_kind(&self) -> Option; +} + +impl TryIoErrorKind for T { + fn try_io_error_kind(&self) -> Option { + Some(self.io_error_kind()) + } +} + +pub trait IoResultKindHintExt: Sized { + type Error; + fn io_err_kind_hint(self) -> Result; +} + +impl IoResultKindHintExt for Result { + type Error = E; + fn io_err_kind_hint(self) -> Result { + self.map_err(|e| { + let kind = e.borrow().io_error_kind(); + (e, kind) + }) + } +} + +pub trait TryIoResultKindHintExt: Sized { + type Error; + fn try_io_err_kind_hint(self) -> Result)>; +} + +impl TryIoResultKindHintExt for Result { + type Error = E; + fn try_io_err_kind_hint(self) -> Result)> { + self.map_err(|e| { + let opt_kind = e.borrow().try_io_error_kind(); + (e, opt_kind) + }) + } +} + +/// Automatically handles `std::io::ErrorKind::Interrupted`. +/// +/// - If there is no error (i.e. on `Ok(r)`), the function will return `Ok(Some(r))` +/// - `Interrupted` is handled internally, by retrying the IO operation +/// - Other errors are returned as is +pub fn handle_interrupted(mut iofn: F) -> Result, E> +where + E: TryIoErrorKind, + F: FnMut() -> Result, +{ + use io::ErrorKind as E; + loop { + match iofn().try_io_err_kind_hint() { + Ok(v) => return Ok(Some(v)), + Err((_, Some(E::Interrupted))) => continue, // try again + Err((e, _)) => return Err(e), + }; + } +} + +/// Automatically handles `std::io::ErrorKind::{WouldBlock, Interrupted}`. +/// +/// - If there is no error (i.e. on `Ok(r)`), the function will return `Ok(Some(r))` +/// - `Interrupted` is handled internally, by retrying the IO operation +/// - `WouldBlock` is handled by returning `Ok(None)`, +/// - Other errors are returned as is +pub fn nonblocking_handle_io_errors(mut iofn: F) -> Result, E> +where + E: TryIoErrorKind, + F: FnMut() -> Result, +{ + use io::ErrorKind as E; + loop { + match iofn().try_io_err_kind_hint() { + Ok(v) => return Ok(Some(v)), + Err((_, Some(E::WouldBlock))) => return Ok(None), // no data to read + Err((_, Some(E::Interrupted))) => continue, // try again + Err((e, _)) => return Err(e), + }; + } +} + +pub trait ReadNonblockingWithBoringErrorsHandledExt { + /// Convenience wrapper using [nonblocking_handle_io_errors] with [std::io::Read] + fn read_nonblocking_with_boring_errors_handled( + &mut self, + buf: &mut [u8], + ) -> io::Result>; +} + +impl ReadNonblockingWithBoringErrorsHandledExt for T { + fn read_nonblocking_with_boring_errors_handled( + &mut self, + buf: &mut [u8], + ) -> io::Result> { + nonblocking_handle_io_errors(|| self.read(buf)) + } +} diff --git a/util/src/length_prefix_encoding/decoder.rs b/util/src/length_prefix_encoding/decoder.rs new file mode 100644 index 00000000..1769b704 --- /dev/null +++ b/util/src/length_prefix_encoding/decoder.rs @@ -0,0 +1,359 @@ +use std::{borrow::BorrowMut, cmp::min, io}; + +use thiserror::Error; +use zeroize::Zeroize; + +use crate::{ + io::{TryIoErrorKind, TryIoResultKindHintExt}, + result::ensure_or, +}; + +pub const HEADER_SIZE: usize = std::mem::size_of::(); + +#[derive(Error, Debug)] +pub enum SanityError { + #[error("Offset is out of read buffer bounds")] + OutOfBufferBounds, + #[error("Offset is out of message buffer bounds")] + OutOfMessageBounds, +} + +#[derive(Error, Debug)] +#[error("Message too large ({msg_size} bytes) for buffer ({buf_size} bytes)")] +pub struct MessageTooLargeError { + msg_size: usize, + buf_size: usize, +} + +impl MessageTooLargeError { + pub fn new(msg_size: usize, buf_size: usize) -> Self { + Self { msg_size, buf_size } + } + + pub fn ensure(msg_size: usize, buf_size: usize) -> Result<(), Self> { + let err = MessageTooLargeError { msg_size, buf_size }; + ensure_or(msg_size <= buf_size, err) + } +} + +#[derive(Debug)] +pub struct ReadFromIoReturn<'a> { + pub bytes_read: usize, + pub message: Option<&'a mut [u8]>, +} + +impl<'a> ReadFromIoReturn<'a> { + pub fn new(bytes_read: usize, message: Option<&'a mut [u8]>) -> Self { + Self { + bytes_read, + message, + } + } +} + +#[derive(Debug, Error)] +pub enum ReadFromIoError { + #[error("Error reading from the underlying stream")] + IoError(#[from] io::Error), + #[error("Message size out of buffer bounds")] + MessageTooLargeError(#[from] MessageTooLargeError), +} + +impl TryIoErrorKind for ReadFromIoError { + fn try_io_error_kind(&self) -> Option { + match self { + ReadFromIoError::IoError(ioe) => Some(ioe.kind()), + _ => None, + } + } +} + +#[derive(Debug, Default, Clone)] +pub struct LengthPrefixDecoder> { + header: [u8; HEADER_SIZE], + buf: Buf, + off: usize, +} + +impl> LengthPrefixDecoder { + pub fn new(buf: Buf) -> Self { + let header = Default::default(); + let off = 0; + Self { header, buf, off } + } + + pub fn clear(&mut self) { + self.zeroize() + } + + pub fn from_parts(header: [u8; HEADER_SIZE], buf: Buf, off: usize) -> Self { + Self { header, buf, off } + } + + pub fn into_parts(self) -> ([u8; HEADER_SIZE], Buf, usize) { + let Self { header, buf, off } = self; + (header, buf, off) + } + + pub fn read_all_from_stdio( + &mut self, + mut r: R, + ) -> Result<&mut [u8], ReadFromIoError> { + use io::ErrorKind as K; + loop { + match self.read_from_stdio(&mut r).try_io_err_kind_hint() { + // Success (appeasing the borrow checker by calling message_mut()) + Ok(ReadFromIoReturn { + message: Some(_), .. + }) => break Ok(self.message_mut().unwrap().unwrap()), + + // Unexpected EOF + Ok(ReadFromIoReturn { bytes_read: 0, .. }) => { + break Err(ReadFromIoError::IoError(io::Error::new( + K::UnexpectedEof, + "", + ))) + } + + // Retry + Ok(ReadFromIoReturn { message: None, .. }) => continue, + Err((_, Some(K::Interrupted))) => continue, + + // Other error + Err((e, _)) => break Err(e), + } + } + } + + pub fn read_from_stdio( + &mut self, + mut r: R, + ) -> Result { + Ok(match self.next_slice_to_write_to()? { + // Read some bytes; any MessageTooLargeError in the call to self.message_mut() is + // ignored to ensure this function changes no state upon errors; the user should rerun + // the function and collect the MessageTooLargeError on the following invocation + Some(buf) => { + let bytes_read = r.read(buf)?; + self.advance(bytes_read).unwrap(); + let message = self.message_mut().ok().flatten(); + ReadFromIoReturn { + bytes_read, + message, + } + } + // Message is already fully read; full delegation to self.message_mut() + None => ReadFromIoReturn { + bytes_read: 0, + message: self.message_mut()?, + }, + }) + } + + pub fn next_slice_to_write_to(&mut self) -> Result, MessageTooLargeError> { + fn some_if_nonempty(buf: &mut [u8]) -> Option<&mut [u8]> { + match buf.is_empty() { + true => None, + false => Some(buf), + } + } + + macro_rules! return_if_nonempty_some { + ($opt:expr) => {{ + // Deliberate double expansion of $opt to appease the borrow checker *sigh* + if $opt.and_then(some_if_nonempty).is_some() { + return Ok($opt); + } + }}; + } + + return_if_nonempty_some!(Some(self.header_buffer_left_mut())); + return_if_nonempty_some!(self.message_fragment_left_mut()?); + Ok(None) + } + + pub fn advance(&mut self, count: usize) -> Result<(), SanityError> { + let off = self.off + count; + let msg_off = off.saturating_sub(HEADER_SIZE); + + use SanityError as E; + let alloc = self.message_buffer().len(); + let msgsz = self.message_size(); + ensure_or(msg_off <= alloc, E::OutOfBufferBounds)?; + ensure_or( + msgsz.map(|s| msg_off <= s).unwrap_or(true), + E::OutOfMessageBounds, + )?; + + self.off = off; + Ok(()) + } + + pub fn ensure_sufficient_msg_buffer(&self) -> Result<(), MessageTooLargeError> { + let buf_size = self.message_buffer().len(); + let msg_size = match self.get_header() { + None => return Ok(()), + Some(v) => v, + }; + MessageTooLargeError::ensure(msg_size, buf_size) + } + + pub fn header_buffer(&self) -> &[u8] { + &self.header[..] + } + + pub fn header_buffer_mut(&mut self) -> &mut [u8] { + &mut self.header[..] + } + + pub fn message_buffer(&self) -> &[u8] { + self.buf.borrow() + } + + pub fn message_buffer_mut(&mut self) -> &mut [u8] { + self.buf.borrow_mut() + } + + pub fn bytes_read(&self) -> &usize { + &self.off + } + + pub fn into_message_buffer(self) -> Buf { + let Self { buf, .. } = self; + buf + } + + pub fn header_buffer_offset(&self) -> usize { + min(self.off, HEADER_SIZE) + } + + pub fn message_buffer_offset(&self) -> usize { + self.off.saturating_sub(HEADER_SIZE) + } + + pub fn has_header(&self) -> bool { + self.header_buffer_offset() == HEADER_SIZE + } + + pub fn has_message(&self) -> Result { + self.ensure_sufficient_msg_buffer()?; + let msg_size = match self.get_header() { + None => return Ok(false), + Some(v) => v, + }; + Ok(self.message_buffer_avail().len() == msg_size) + } + + pub fn header_buffer_avail(&self) -> &[u8] { + let off = self.header_buffer_offset(); + &self.header_buffer()[..off] + } + + pub fn header_buffer_avail_mut(&mut self) -> &mut [u8] { + let off = self.header_buffer_offset(); + &mut self.header_buffer_mut()[..off] + } + + pub fn header_buffer_left(&self) -> &[u8] { + let off = self.header_buffer_offset(); + &self.header_buffer()[off..] + } + + pub fn header_buffer_left_mut(&mut self) -> &mut [u8] { + let off = self.header_buffer_offset(); + &mut self.header_buffer_mut()[off..] + } + + pub fn message_buffer_avail(&self) -> &[u8] { + let off = self.message_buffer_offset(); + &self.message_buffer()[..off] + } + + pub fn message_buffer_avail_mut(&mut self) -> &mut [u8] { + let off = self.message_buffer_offset(); + &mut self.message_buffer_mut()[..off] + } + + pub fn message_buffer_left(&self) -> &[u8] { + let off = self.message_buffer_offset(); + &self.message_buffer()[off..] + } + + pub fn message_buffer_left_mut(&mut self) -> &mut [u8] { + let off = self.message_buffer_offset(); + &mut self.message_buffer_mut()[off..] + } + + pub fn get_header(&self) -> Option { + match self.header_buffer_offset() == HEADER_SIZE { + false => None, + true => Some(u64::from_le_bytes(self.header) as usize), + } + } + + pub fn message_size(&self) -> Option { + self.get_header() + } + + pub fn encoded_message_bytes(&self) -> Option { + self.message_size().map(|sz| sz + HEADER_SIZE) + } + + pub fn message_fragment(&self) -> Result, MessageTooLargeError> { + self.ensure_sufficient_msg_buffer()?; + Ok(self.message_size().map(|sz| &self.message_buffer()[..sz])) + } + + pub fn message_fragment_mut(&mut self) -> Result, MessageTooLargeError> { + self.ensure_sufficient_msg_buffer()?; + Ok(self + .message_size() + .map(|sz| &mut self.message_buffer_mut()[..sz])) + } + + pub fn message_fragment_avail(&self) -> Result, MessageTooLargeError> { + let off = self.message_buffer_avail().len(); + self.message_fragment() + .map(|frag| frag.map(|frag| &frag[..off])) + } + + pub fn message_fragment_avail_mut( + &mut self, + ) -> Result, MessageTooLargeError> { + let off = self.message_buffer_avail().len(); + self.message_fragment_mut() + .map(|frag| frag.map(|frag| &mut frag[..off])) + } + + pub fn message_fragment_left(&self) -> Result, MessageTooLargeError> { + let off = self.message_buffer_avail().len(); + self.message_fragment() + .map(|frag| frag.map(|frag| &frag[off..])) + } + + pub fn message_fragment_left_mut(&mut self) -> Result, MessageTooLargeError> { + let off = self.message_buffer_avail().len(); + self.message_fragment_mut() + .map(|frag| frag.map(|frag| &mut frag[off..])) + } + + pub fn message(&self) -> Result, MessageTooLargeError> { + let sz = self.message_size(); + self.message_fragment_avail() + .map(|frag_opt| frag_opt.and_then(|frag| (frag.len() == sz?).then_some(frag))) + } + + pub fn message_mut(&mut self) -> Result, MessageTooLargeError> { + let sz = self.message_size(); + self.message_fragment_avail_mut() + .map(|frag_opt| frag_opt.and_then(|frag| (frag.len() == sz?).then_some(frag))) + } +} + +impl> Zeroize for LengthPrefixDecoder { + fn zeroize(&mut self) { + self.header.zeroize(); + self.message_buffer_mut().zeroize(); + self.off.zeroize(); + } +} diff --git a/util/src/length_prefix_encoding/encoder.rs b/util/src/length_prefix_encoding/encoder.rs new file mode 100644 index 00000000..a7f86b57 --- /dev/null +++ b/util/src/length_prefix_encoding/encoder.rs @@ -0,0 +1,381 @@ +use std::{ + borrow::{Borrow, BorrowMut}, + cmp::min, + io, +}; + +use thiserror::Error; +use zeroize::Zeroize; + +use crate::{io::IoResultKindHintExt, result::ensure_or}; + +pub const HEADER_SIZE: usize = std::mem::size_of::(); + +#[derive(Error, Debug, Clone, Copy)] +#[error("Write position is out of buffer bounds")] +pub struct PositionOutOfBufferBounds; + +#[derive(Error, Debug, Clone, Copy)] +#[error("Write position is out of message bounds")] +pub struct PositionOutOfMessageBounds; + +#[derive(Error, Debug, Clone, Copy)] +#[error("Write position is out of header bounds")] +pub struct PositionOutOfHeaderBounds; + +#[derive(Error, Debug, Clone, Copy)] +#[error("Message length is bigger than buffer length")] +pub struct MessageTooLarge; + +#[derive(Error, Debug, Clone, Copy)] +pub enum MessageLenSanityError { + #[error("{0:?}")] + PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds), + #[error("{0:?}")] + MessageTooLarge(#[from] MessageTooLarge), +} + +#[derive(Error, Debug, Clone, Copy)] +pub enum PositionSanityError { + #[error("{0:?}")] + PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds), + #[error("{0:?}")] + PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds), +} + +#[derive(Error, Debug, Clone, Copy)] +pub enum SanityError { + #[error("{0:?}")] + PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds), + #[error("{0:?}")] + PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds), + #[error("{0:?}")] + MessageTooLarge(#[from] MessageTooLarge), +} + +impl TryFrom for MessageLenSanityError { + type Error = PositionOutOfBufferBounds; + + fn try_from(value: SanityError) -> Result { + use {MessageLenSanityError as T, SanityError as F}; + match value { + F::PositionOutOfMessageBounds(e) => Ok(T::PositionOutOfMessageBounds(e)), + F::MessageTooLarge(e) => Ok(T::MessageTooLarge(e)), + F::PositionOutOfBufferBounds(e) => Err(e), + } + } +} + +impl From for SanityError { + fn from(value: MessageLenSanityError) -> Self { + use {MessageLenSanityError as F, SanityError as T}; + match value { + F::PositionOutOfMessageBounds(e) => T::PositionOutOfMessageBounds(e), + F::MessageTooLarge(e) => T::MessageTooLarge(e), + } + } +} + +impl From for SanityError { + fn from(value: PositionSanityError) -> Self { + use {PositionSanityError as F, SanityError as T}; + match value { + F::PositionOutOfBufferBounds(e) => T::PositionOutOfBufferBounds(e), + F::PositionOutOfMessageBounds(e) => T::PositionOutOfMessageBounds(e), + } + } +} + +pub struct WriteToIoReturn { + pub bytes_written: usize, + pub done: bool, +} + +#[derive(Clone, Copy, Debug)] +pub struct LengthPrefixEncoder> { + buf: Buf, + header: [u8; HEADER_SIZE], + pos: usize, +} + +impl> LengthPrefixEncoder { + pub fn from_buffer(buf: Buf) -> Self { + let (header, pos) = ([0u8; HEADER_SIZE], 0); + let mut r = Self { buf, header, pos }; + r.clear(); + r + } + + pub fn from_message(msg: Buf) -> Self { + let mut r = Self::from_buffer(msg); + r.restart_write_with_new_message(r.buffer_bytes().len()) + .unwrap(); + r + } + + pub fn from_short_message(msg: Buf, len: usize) -> Result { + let mut r = Self::from_message(msg); + r.set_message_len(len)?; + Ok(r) + } + + pub fn from_parts(buf: Buf, len: usize, pos: usize) -> Result { + let mut r = Self::from_buffer(buf); + r.set_msg_len_and_position(len, pos)?; + Ok(r) + } + + pub fn into_buffer(self) -> Buf { + let Self { buf, .. } = self; + buf + } + + pub fn into_parts(self) -> (Buf, usize, usize) { + let len = self.message_len(); + let pos = self.writing_position(); + let buf = self.into_buffer(); + (buf, len, pos) + } + + pub fn clear(&mut self) { + self.set_msg_len_and_position(0, 0).unwrap(); + self.set_message_offset(0).unwrap(); + } + + pub fn write_all_to_stdio(&mut self, mut w: W) -> io::Result<()> { + use io::ErrorKind as K; + loop { + match self.write_to_stdio(&mut w).io_err_kind_hint() { + // Done + Ok(WriteToIoReturn { done: true, .. }) => break Ok(()), + + // Retry + Ok(WriteToIoReturn { done: false, .. }) => continue, + Err((_, K::Interrupted)) => continue, + + Err((e, _)) => break Err(e), + } + } + } + + pub fn write_to_stdio(&mut self, mut w: W) -> io::Result { + if self.exhausted() { + return Ok(WriteToIoReturn { + bytes_written: 0, + done: true, + }); + } + + let buf = self.next_slice_to_write(); + let bytes_written = w.write(buf)?; + self.advance(bytes_written).unwrap(); + + let done = self.exhausted(); + Ok(WriteToIoReturn { + bytes_written, + done, + }) + } + + pub fn restart_write(&mut self) { + self.set_writing_position(0).unwrap() + } + + pub fn restart_write_with_new_message( + &mut self, + len: usize, + ) -> Result<(), MessageLenSanityError> { + self.set_msg_len_and_position(len, 0) + .map_err(|e| e.try_into().unwrap()) + } + + pub fn next_slice_to_write(&self) -> &[u8] { + let s = self.header_left(); + if !s.is_empty() { + return s; + } + + let s = self.message_left(); + if !s.is_empty() { + return s; + } + + &[] + } + + pub fn exhausted(&self) -> bool { + self.next_slice_to_write().is_empty() + } + + pub fn message(&self) -> &[u8] { + &self.buffer_bytes()[..self.message_len()] + } + + pub fn header_written(&self) -> &[u8] { + &self.header()[..self.header_offset()] + } + + pub fn header_left(&self) -> &[u8] { + &self.header()[self.header_offset()..] + } + + pub fn message_written(&self) -> &[u8] { + &self.message()[..self.message_offset()] + } + + pub fn message_left(&self) -> &[u8] { + &self.message()[self.message_offset()..] + } + + pub fn buf(&self) -> &Buf { + &self.buf + } + + pub fn buffer_bytes(&self) -> &[u8] { + self.buf().borrow() + } + + pub fn decode_header(&self) -> u64 { + u64::from_le_bytes(self.header) + } + + pub fn header(&self) -> &[u8; HEADER_SIZE] { + &self.header + } + + pub fn message_len(&self) -> usize { + self.decode_header() as usize + } + + pub fn encoded_message_bytes(&self) -> usize { + self.message_len() + HEADER_SIZE + } + + pub fn writing_position(&self) -> usize { + self.pos + } + + pub fn header_offset(&self) -> usize { + min(self.writing_position(), HEADER_SIZE) + } + + pub fn message_offset(&self) -> usize { + self.writing_position().saturating_sub(HEADER_SIZE) + } + + pub fn set_header(&mut self, header: [u8; HEADER_SIZE]) -> Result<(), MessageLenSanityError> { + self.offset_transaction(|t| { + t.header = header; + t.ensure_msg_in_buf_bounds()?; + t.ensure_pos_in_msg_bounds()?; + Ok(()) + }) + } + + pub fn encode_and_set_header(&mut self, header: u64) -> Result<(), MessageLenSanityError> { + self.set_header(header.to_le_bytes()) + } + + pub fn set_message_len(&mut self, len: usize) -> Result<(), MessageLenSanityError> { + self.encode_and_set_header(len as u64) + } + + pub fn set_writing_position(&mut self, pos: usize) -> Result<(), PositionSanityError> { + self.offset_transaction(|t| { + t.pos = pos; + t.ensure_pos_in_buf_bounds()?; + t.ensure_pos_in_msg_bounds()?; + Ok(()) + }) + } + + pub fn set_header_offset(&mut self, off: usize) -> Result<(), PositionOutOfHeaderBounds> { + ensure_or(off <= HEADER_SIZE, PositionOutOfHeaderBounds)?; + self.set_writing_position(off).unwrap(); + Ok(()) + } + + pub fn set_message_offset(&mut self, off: usize) -> Result<(), PositionSanityError> { + self.set_writing_position(off + HEADER_SIZE) + } + + pub fn advance(&mut self, off: usize) -> Result<(), PositionSanityError> { + self.set_writing_position(self.writing_position() + off) + } + + pub fn set_msg_len_and_position(&mut self, len: usize, pos: usize) -> Result<(), SanityError> { + self.pos = 0; + self.set_message_len(len)?; + self.set_writing_position(pos)?; + Ok(()) + } + + fn offset_transaction(&mut self, f: F) -> Result<(), E> + where + F: FnOnce(&mut LengthPrefixEncoder<&[u8]>) -> Result<(), E>, + { + let (header, pos) = { + let (buf, header, pos) = (self.buffer_bytes(), self.header, self.pos); + let mut tmp = LengthPrefixEncoder { buf, header, pos }; + f(&mut tmp)?; + Ok((tmp.header, tmp.pos)) + }?; + (self.header, self.pos) = (header, pos); + Ok(()) + } + + fn ensure_pos_in_buf_bounds(&self) -> Result<(), PositionOutOfBufferBounds> { + ensure_or( + self.message_offset() <= self.buffer_bytes().len(), + PositionOutOfBufferBounds, + ) + } + + fn ensure_pos_in_msg_bounds(&self) -> Result<(), PositionOutOfMessageBounds> { + ensure_or( + self.message_offset() <= self.message_len(), + PositionOutOfMessageBounds, + ) + } + + fn ensure_msg_in_buf_bounds(&self) -> Result<(), MessageTooLarge> { + ensure_or( + self.message_len() <= self.buffer_bytes().len(), + MessageTooLarge, + ) + } +} + +impl> LengthPrefixEncoder { + pub fn buf_mut(&mut self) -> &mut Buf { + &mut self.buf + } + + pub fn buffer_bytes_mut(&mut self) -> &mut [u8] { + self.buf.borrow_mut() + } + + pub fn message_mut(&mut self) -> &mut [u8] { + let off = self.message_len(); + &mut self.buffer_bytes_mut()[..off] + } + + pub fn message_written_mut(&mut self) -> &mut [u8] { + let off = self.message_offset(); + &mut self.message_mut()[..off] + } + + pub fn message_left_mut(&mut self) -> &mut [u8] { + let off = self.message_offset(); + &mut self.message_mut()[off..] + } +} + +impl> Zeroize for LengthPrefixEncoder { + fn zeroize(&mut self) { + self.buffer_bytes_mut().zeroize(); + self.header.zeroize(); + self.pos.zeroize(); + self.clear(); + } +} diff --git a/util/src/length_prefix_encoding/mod.rs b/util/src/length_prefix_encoding/mod.rs new file mode 100644 index 00000000..a4d5bf51 --- /dev/null +++ b/util/src/length_prefix_encoding/mod.rs @@ -0,0 +1,2 @@ +pub mod decoder; +pub mod encoder; diff --git a/util/src/lib.rs b/util/src/lib.rs index 637de5d1..c4d3f313 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -4,8 +4,13 @@ pub mod b64; pub mod fd; pub mod file; pub mod functional; +pub mod io; +pub mod length_prefix_encoding; pub mod mem; +pub mod mio; pub mod ord; pub mod result; pub mod time; pub mod typenum; +pub mod zerocopy; +pub mod zeroize; diff --git a/util/src/mem.rs b/util/src/mem.rs index be3d0e81..620e3d89 100644 --- a/util/src/mem.rs +++ b/util/src/mem.rs @@ -1,5 +1,7 @@ use std::borrow::{Borrow, BorrowMut}; use std::cmp::min; +use std::mem::{forget, swap}; +use std::ops::{Deref, DerefMut}; /// Concatenate two byte arrays // TODO: Zeroize result? @@ -31,3 +33,62 @@ pub fn cpy_min + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, d let len = min(src.len(), dst.len()); dst[..len].copy_from_slice(&src[..len]); } + +/// Wrapper type to inhibit calling [std::mem::Drop] when the underlying variable is freed +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Default)] +pub struct Forgetting { + value: Option, +} + +impl Forgetting { + pub fn new(value: T) -> Self { + let value = Some(value); + Self { value } + } + + pub fn extract(mut self) -> T { + let mut value = None; + swap(&mut value, &mut self.value); + value.unwrap() + } +} + +impl From for Forgetting { + fn from(value: T) -> Self { + Self::new(value) + } +} + +impl Deref for Forgetting { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value.as_ref().unwrap() + } +} + +impl DerefMut for Forgetting { + fn deref_mut(&mut self) -> &mut Self::Target { + self.value.as_mut().unwrap() + } +} + +impl Borrow for Forgetting { + fn borrow(&self) -> &T { + self.deref() + } +} + +impl BorrowMut for Forgetting { + fn borrow_mut(&mut self) -> &mut T { + self.deref_mut() + } +} + +impl Drop for Forgetting { + fn drop(&mut self) { + let mut value = None; + swap(&mut self.value, &mut value); + forget(value) + } +} diff --git a/util/src/mio.rs b/util/src/mio.rs new file mode 100644 index 00000000..10757eec --- /dev/null +++ b/util/src/mio.rs @@ -0,0 +1,39 @@ +use mio::net::{UnixListener, UnixStream}; +use rustix::fd::RawFd; + +use crate::fd::claim_fd; + +pub mod interest { + use mio::Interest; + pub const R: Interest = Interest::READABLE; + pub const W: Interest = Interest::WRITABLE; + pub const RW: Interest = R.add(W); +} + +pub trait UnixListenerExt: Sized { + fn claim_fd(fd: RawFd) -> anyhow::Result; +} + +impl UnixListenerExt for UnixListener { + fn claim_fd(fd: RawFd) -> anyhow::Result { + use std::os::unix::net::UnixListener as StdUnixListener; + + let sock = StdUnixListener::from(claim_fd(fd)?); + sock.set_nonblocking(true)?; + Ok(UnixListener::from_std(sock)) + } +} + +pub trait UnixStreamExt: Sized { + fn claim_fd(fd: RawFd) -> anyhow::Result; +} + +impl UnixStreamExt for UnixStream { + fn claim_fd(fd: RawFd) -> anyhow::Result { + use std::os::unix::net::UnixStream as StdUnixStream; + + let sock = StdUnixStream::from(claim_fd(fd)?); + sock.set_nonblocking(true)?; + Ok(UnixStream::from_std(sock)) + } +} diff --git a/util/src/result.rs b/util/src/result.rs index 8f0f258f..76b60629 100644 --- a/util/src/result.rs +++ b/util/src/result.rs @@ -1,5 +1,4 @@ use std::convert::Infallible; -use std::result::Result; /// Try block basically…returns a result and allows the use of the question mark operator inside #[macro_export] @@ -97,3 +96,14 @@ impl GuaranteedValue for Guaranteed { self.unwrap() } } + +pub fn ensure_or(b: bool, err: E) -> Result<(), E> { + match b { + true => Ok(()), + false => Err(err), + } +} + +pub fn bail_if(b: bool, err: E) -> Result<(), E> { + ensure_or(!b, err) +} diff --git a/util/src/typenum.rs b/util/src/typenum.rs index 94fe0fd0..2f1d32fd 100644 --- a/util/src/typenum.rs +++ b/util/src/typenum.rs @@ -19,15 +19,22 @@ pub trait IntoConst { const VALUE: T; } +#[allow(dead_code)] struct ConstApplyNegSign::Type>>( *const T, *const Param, ); + +#[allow(dead_code)] struct ConstApplyPosSign::Type>>( *const T, *const Param, ); + +#[allow(dead_code)] struct ConstLshift, const SHIFT: i32>(*const T, *const Param); // impl IntoConst + +#[allow(dead_code)] struct ConstAdd, Rhs: IntoConst>(*const T, *const Lhs, *const Rhs); // impl IntoConst /// Assigns an unsigned type to a signed type diff --git a/util/src/zerocopy/mod.rs b/util/src/zerocopy/mod.rs new file mode 100644 index 00000000..6856bea3 --- /dev/null +++ b/util/src/zerocopy/mod.rs @@ -0,0 +1,7 @@ +mod ref_maker; +mod zerocopy_ref_ext; +mod zerocopy_slice_ext; + +pub use ref_maker::*; +pub use zerocopy_ref_ext::*; +pub use zerocopy_slice_ext::*; diff --git a/util/src/zerocopy/ref_maker.rs b/util/src/zerocopy/ref_maker.rs new file mode 100644 index 00000000..0b18702a --- /dev/null +++ b/util/src/zerocopy/ref_maker.rs @@ -0,0 +1,107 @@ +use std::marker::PhantomData; + +use anyhow::{ensure, Context}; +use zerocopy::{ByteSlice, ByteSliceMut, Ref}; +use zeroize::Zeroize; + +use crate::zeroize::ZeroizedExt; + +#[derive(Clone, Copy, Debug)] +pub struct RefMaker { + buf: B, + _phantom_t: PhantomData, +} + +impl RefMaker { + pub fn new(buf: B) -> Self { + let _phantom_t = PhantomData; + Self { buf, _phantom_t } + } + + pub const fn target_size() -> usize { + std::mem::size_of::() + } + + pub fn into_buf(self) -> B { + self.buf + } + + pub fn buf(&self) -> &B { + &self.buf + } + + pub fn buf_mut(&mut self) -> &mut B { + &mut self.buf + } +} + +impl RefMaker { + pub fn parse(self) -> anyhow::Result> { + self.ensure_fit()?; + Ref::::new(self.buf).context("Parser error!") + } + + pub fn from_prefix_with_tail(self) -> anyhow::Result<(Self, B)> { + self.ensure_fit()?; + let (head, tail) = self.buf.split_at(Self::target_size()); + Ok((Self::new(head), tail)) + } + + pub fn split_prefix(self) -> anyhow::Result<(Self, Self)> { + self.ensure_fit()?; + let (head, tail) = self.buf.split_at(Self::target_size()); + Ok((Self::new(head), Self::new(tail))) + } + + pub fn from_prefix(self) -> anyhow::Result { + Ok(Self::from_prefix_with_tail(self)?.0) + } + + pub fn from_suffix_with_head(self) -> anyhow::Result<(Self, B)> { + self.ensure_fit()?; + let point = self.bytes().len() - Self::target_size(); + let (head, tail) = self.buf.split_at(point); + Ok((Self::new(tail), head)) + } + + pub fn split_suffix(self) -> anyhow::Result<(Self, Self)> { + self.ensure_fit()?; + let point = self.bytes().len() - Self::target_size(); + let (head, tail) = self.buf.split_at(point); + Ok((Self::new(head), Self::new(tail))) + } + + pub fn from_suffix(self) -> anyhow::Result { + Ok(Self::from_suffix_with_head(self)?.0) + } + + pub fn bytes(&self) -> &[u8] { + self.buf().deref() + } + + pub fn ensure_fit(&self) -> anyhow::Result<()> { + let have = self.bytes().len(); + let need = Self::target_size(); + ensure!( + need <= have, + "Buffer is undersized at {have} bytes (need {need} bytes)!" + ); + Ok(()) + } +} + +impl RefMaker { + pub fn make_zeroized(self) -> anyhow::Result> { + self.zeroized().parse() + } + + pub fn bytes_mut(&mut self) -> &mut [u8] { + self.buf_mut().deref_mut() + } +} + +impl Zeroize for RefMaker { + fn zeroize(&mut self) { + self.bytes_mut().zeroize() + } +} diff --git a/util/src/zerocopy/zerocopy_ref_ext.rs b/util/src/zerocopy/zerocopy_ref_ext.rs new file mode 100644 index 00000000..1acc52af --- /dev/null +++ b/util/src/zerocopy/zerocopy_ref_ext.rs @@ -0,0 +1,27 @@ +use zerocopy::{ByteSlice, ByteSliceMut, Ref}; + +pub trait ZerocopyEmancipateExt { + fn emancipate(&self) -> Ref<&[u8], T>; +} + +pub trait ZerocopyEmancipateMutExt { + fn emancipate_mut(&mut self) -> Ref<&mut [u8], T>; +} + +impl ZerocopyEmancipateExt for Ref +where + B: ByteSlice, +{ + fn emancipate(&self) -> Ref<&[u8], T> { + Ref::new(self.bytes()).unwrap() + } +} + +impl ZerocopyEmancipateMutExt for Ref +where + B: ByteSliceMut, +{ + fn emancipate_mut(&mut self) -> Ref<&mut [u8], T> { + Ref::new(self.bytes_mut()).unwrap() + } +} diff --git a/util/src/zerocopy/zerocopy_slice_ext.rs b/util/src/zerocopy/zerocopy_slice_ext.rs new file mode 100644 index 00000000..13992539 --- /dev/null +++ b/util/src/zerocopy/zerocopy_slice_ext.rs @@ -0,0 +1,39 @@ +use zerocopy::{ByteSlice, ByteSliceMut, Ref}; + +use super::RefMaker; + +pub trait ZerocopySliceExt: Sized + ByteSlice { + fn zk_ref_maker(self) -> RefMaker { + RefMaker::::new(self) + } + + fn zk_parse(self) -> anyhow::Result> { + self.zk_ref_maker().parse() + } + + fn zk_parse_prefix(self) -> anyhow::Result> { + self.zk_ref_maker().from_prefix()?.parse() + } + + fn zk_parse_suffix(self) -> anyhow::Result> { + self.zk_ref_maker().from_suffix()?.parse() + } +} + +impl ZerocopySliceExt for B {} + +pub trait ZerocopyMutSliceExt: ZerocopySliceExt + Sized + ByteSliceMut { + fn zk_zeroized(self) -> anyhow::Result> { + self.zk_ref_maker().make_zeroized() + } + + fn zk_zeroized_from_prefix(self) -> anyhow::Result> { + self.zk_ref_maker().from_prefix()?.make_zeroized() + } + + fn zk_zeroized_from_suffix(self) -> anyhow::Result> { + self.zk_ref_maker().from_suffix()?.make_zeroized() + } +} + +impl ZerocopyMutSliceExt for B {} diff --git a/util/src/zeroize/mod.rs b/util/src/zeroize/mod.rs new file mode 100644 index 00000000..f1d37be1 --- /dev/null +++ b/util/src/zeroize/mod.rs @@ -0,0 +1,2 @@ +mod zeroized_ext; +pub use zeroized_ext::*; diff --git a/util/src/zeroize/zeroized_ext.rs b/util/src/zeroize/zeroized_ext.rs new file mode 100644 index 00000000..c4f87d5f --- /dev/null +++ b/util/src/zeroize/zeroized_ext.rs @@ -0,0 +1,10 @@ +use zeroize::Zeroize; + +pub trait ZeroizedExt: Zeroize + Sized { + fn zeroized(mut self) -> Self { + self.zeroize(); + self + } +} + +impl ZeroizedExt for T {} diff --git a/wireguard-broker/src/api/client.rs b/wireguard-broker/src/api/client.rs index f27c3355..73dbb066 100644 --- a/wireguard-broker/src/api/client.rs +++ b/wireguard-broker/src/api/client.rs @@ -88,7 +88,7 @@ where None => return Ok(None), }; - let typ = res.get(0).ok_or(invalid_msg_poller())?; + let typ = res.first().ok_or(invalid_msg_poller())?; let typ = msgs::MsgType::try_from(*typ)?; let msgs::MsgType::SetPsk = typ; // Assert type @@ -113,7 +113,7 @@ where fn set_psk(&mut self, config: SerializedBrokerConfig) -> Result<(), Self::Error> { let config: Result = config.try_into(); - let config = config.map_err(|e| BrokerClientSetPskError::BrokerError(e))?; + let config = config.map_err(BrokerClientSetPskError::BrokerError)?; use BrokerClientSetPskError::*; const BUF_SIZE: usize = REQUEST_MSG_BUFFER_SIZE; diff --git a/wireguard-broker/src/api/config.rs b/wireguard-broker/src/api/config.rs index 097742fe..c487d7fa 100644 --- a/wireguard-broker/src/api/config.rs +++ b/wireguard-broker/src/api/config.rs @@ -11,12 +11,12 @@ pub struct NetworkBrokerConfig<'a> { pub psk: &'a Secret, } -impl<'a> Into> for NetworkBrokerConfig<'a> { - fn into(self) -> SerializedBrokerConfig<'a> { - SerializedBrokerConfig { - interface: self.iface.as_bytes(), - peer_id: self.peer_id, - psk: self.psk, +impl<'a> From> for SerializedBrokerConfig<'a> { + fn from(src: NetworkBrokerConfig<'a>) -> SerializedBrokerConfig<'a> { + Self { + interface: src.iface.as_bytes(), + peer_id: src.peer_id, + psk: src.psk, additional_params: &[], } } diff --git a/wireguard-broker/src/api/msgs.rs b/wireguard-broker/src/api/msgs.rs index 89c6a6d0..4021c5b3 100644 --- a/wireguard-broker/src/api/msgs.rs +++ b/wireguard-broker/src/api/msgs.rs @@ -1,4 +1,3 @@ -use std::result::Result; use std::str::{from_utf8, Utf8Error}; use zerocopy::{AsBytes, FromBytes, FromZeroes}; @@ -43,7 +42,7 @@ impl SetPskRequest { self.iface_size = iface.len() as u8; self.iface_buf = [0; 255]; - (&mut self.iface_buf[..iface.len()]).copy_from_slice(iface); + self.iface_buf[..iface.len()].copy_from_slice(iface); Some(()) } diff --git a/wireguard-broker/src/api/server.rs b/wireguard-broker/src/api/server.rs index bd3f6a30..1eaf2556 100644 --- a/wireguard-broker/src/api/server.rs +++ b/wireguard-broker/src/api/server.rs @@ -1,5 +1,4 @@ use std::borrow::BorrowMut; -use std::result::Result; use rosenpass_secret_memory::{Public, Secret}; @@ -49,7 +48,7 @@ where ) -> Result { use BrokerServerError::*; - let typ = req.get(0).ok_or(InvalidMessage)?; + let typ = req.first().ok_or(InvalidMessage)?; let typ = msgs::MsgType::try_from(*typ)?; let msgs::MsgType::SetPsk = typ; // Assert type diff --git a/wireguard-broker/src/bin/priviledged.rs b/wireguard-broker/src/bin/priviledged.rs index 131b9ad5..2086cbe0 100644 --- a/wireguard-broker/src/bin/priviledged.rs +++ b/wireguard-broker/src/bin/priviledged.rs @@ -9,7 +9,6 @@ fn main() { #[cfg(target_os = "linux")] pub mod linux { use std::io::{stdin, stdout, Read, Write}; - use std::result::Result; use rosenpass_wireguard_broker::api::msgs; use rosenpass_wireguard_broker::api::server::BrokerServer; @@ -60,7 +59,7 @@ pub mod linux { // Write the response stdout.write_all(&(res.len() as u64).to_le_bytes())?; - stdout.write_all(&res)?; + stdout.write_all(res)?; stdout.flush()?; } } diff --git a/wireguard-broker/src/bin/socket_handler.rs b/wireguard-broker/src/bin/socket_handler.rs index e1693c58..9638ba59 100644 --- a/wireguard-broker/src/bin/socket_handler.rs +++ b/wireguard-broker/src/bin/socket_handler.rs @@ -121,7 +121,7 @@ async fn direct_broker_process( // Read the message itself let mut res_buf = request; // Avoid allocating memory if we don't have to - res_buf.resize(len as usize, 0); + res_buf.resize(len, 0); stdout.read_exact(&mut res_buf[..len]).await?; // Return to the unix socket connection worker @@ -163,7 +163,7 @@ async fn on_accept(queue: mpsc::Sender, mut stream: UnixStream) - ); // Read the message itself - req_buf.resize(len as usize, 0); + req_buf.resize(len, 0); stream.read_exact(&mut req_buf[..len]).await?; // Handle the message diff --git a/wireguard-broker/src/brokers/mio_client.rs b/wireguard-broker/src/brokers/mio_client.rs index 666a0558..8898613d 100644 --- a/wireguard-broker/src/brokers/mio_client.rs +++ b/wireguard-broker/src/brokers/mio_client.rs @@ -52,23 +52,17 @@ impl MioBrokerClient { // This sucks match self.inner.poll_response() { - Ok(res) => { - return Ok(res); - } - Err(BrokerClientPollResponseError::IoError(e)) => { - return Err(e); - } - Err(BrokerClientPollResponseError::InvalidMessage) => { - bail!("Invalid message"); - } - }; + Ok(res) => Ok(res), + Err(BrokerClientPollResponseError::IoError(e)) => Err(e), + Err(BrokerClientPollResponseError::InvalidMessage) => bail!("Invalid message"), + } } } impl WireGuardBroker for MioBrokerClient { type Error = anyhow::Error; - fn set_psk<'a>(&mut self, config: SerializedBrokerConfig<'a>) -> anyhow::Result<()> { + fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> anyhow::Result<()> { use BrokerClientSetPskError::*; let e = self.inner.set_psk(config); match e { @@ -115,7 +109,7 @@ impl BrokerClientIo for MioBrokerClientIo { fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError> { self.flush()?; self.send_or_buffer(&(buf.len() as u64).to_le_bytes())?; - self.send_or_buffer(&buf)?; + self.send_or_buffer(buf)?; self.flush()?; Ok(()) @@ -192,7 +186,7 @@ impl MioBrokerClientIo { self.send_buf.drain(..written); - (&self.socket).try_io(|| (&self.socket).flush())?; + self.socket.try_io(|| (&self.socket).flush())?; res } @@ -204,7 +198,7 @@ impl MioBrokerClientIo { off += raw_send(&self.socket, buf)?; } - self.send_buf.extend((&buf[off..]).iter()); + self.send_buf.extend(buf[off..].iter()); Ok(()) } @@ -231,7 +225,7 @@ fn raw_send(mut socket: &mio::net::UnixStream, data: &[u8]) -> anyhow::Result anyhow::Result { @@ -255,5 +249,5 @@ fn raw_recv(mut socket: &mio::net::UnixStream, out: &mut [u8]) -> anyhow::Result } })?; - return Ok(off); + Ok(off) } diff --git a/wireguard-broker/src/brokers/netlink.rs b/wireguard-broker/src/brokers/netlink.rs index 4c8ffda8..556bd3cb 100644 --- a/wireguard-broker/src/brokers/netlink.rs +++ b/wireguard-broker/src/brokers/netlink.rs @@ -87,21 +87,20 @@ impl WireGuardBroker for NetlinkWireGuardBroker { .sock .get_device(wg::DeviceInterface::from_name(config.iface))?; - if state + if !state .peers .iter() - .find(|p| &p.public_key == &config.peer_id.value) - .is_none() + .any(|p| p.public_key == config.peer_id.value) { return Err(SetPskError::NoSuchPeer); } // Peer update description - let mut set_peer = wireguard_uapi::set::Peer::from_public_key(&config.peer_id); + let mut set_peer = wireguard_uapi::set::Peer::from_public_key(config.peer_id); set_peer .flags .push(wireguard_uapi::linux::set::WgPeerF::UpdateOnly); - set_peer.preshared_key = Some(&config.psk.secret()); + set_peer.preshared_key = Some(config.psk.secret()); // Device update description let mut set_dev = wireguard_uapi::set::Device::from_ifname(config.iface); diff --git a/wireguard-broker/src/lib.rs b/wireguard-broker/src/lib.rs index ee46b674..e9ba4431 100644 --- a/wireguard-broker/src/lib.rs +++ b/wireguard-broker/src/lib.rs @@ -1,5 +1,5 @@ use rosenpass_secret_memory::{Public, Secret}; -use std::{fmt::Debug, result::Result}; +use std::fmt::Debug; pub const WG_KEY_LEN: usize = 32; pub const WG_PEER_LEN: usize = 32; diff --git a/wireguard-broker/tests/integration.rs b/wireguard-broker/tests/integration.rs index 2166aab9..b25885f6 100644 --- a/wireguard-broker/tests/integration.rs +++ b/wireguard-broker/tests/integration.rs @@ -36,6 +36,7 @@ mod integration_tests { impl WireGuardBroker for MockServerBroker { type Error = SetPskError; + #[allow(clippy::clone_on_copy)] fn set_psk(&mut self, config: SerializedBrokerConfig) -> Result<(), Self::Error> { loop { let mut lock = self.inner.try_lock();