diff --git a/rosenpass/Cargo.toml b/rosenpass/Cargo.toml index 2099d92..298cfe3 100644 --- a/rosenpass/Cargo.toml +++ b/rosenpass/Cargo.toml @@ -22,6 +22,10 @@ required-features = ["experiment_api", "internal_bin_gen_ipc_msg_types"] name = "api-integration-tests" required-features = ["experiment_api", "internal_testing"] +[[test]] +name = "api-integration-tests-supply-keypair" +required-features = ["experiment_api", "internal_testing"] + [[bench]] name = "handshake" harness = false @@ -67,6 +71,7 @@ stacker = { workspace = true } serial_test = {workspace = true} procspawn = {workspace = true} tempfile = { workspace = true } +rustix = {workspace = true} [features] experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"] diff --git a/rosenpass/src/api/api_handler.rs b/rosenpass/src/api/api_handler.rs index fee379d..984ccba 100644 --- a/rosenpass/src/api/api_handler.rs +++ b/rosenpass/src/api/api_handler.rs @@ -1,10 +1,12 @@ use std::{borrow::BorrowMut, collections::VecDeque, os::fd::OwnedFd}; +use anyhow::Context; use rosenpass_to::{ops::copy_slice, To}; +use rosenpass_util::{fd::FdIo, functional::run, io::ReadExt, mem::DiscardResultExt}; -use crate::app_server::AppServer; +use crate::{app_server::AppServer, protocol::BuildCryptoServer}; -use super::Server as ApiServer; +use super::{supply_keypair_response_status, Server as ApiServer}; #[derive(Debug)] pub struct ApiHandler { @@ -25,6 +27,42 @@ pub trait ApiHandlerContext { fn app_server_mut(&mut self) -> &mut AppServer; } +#[derive(thiserror::Error, Debug)] +#[error("Error in SupplyKeypair")] +struct SupplyKeypairError { + status: u128, + #[source] + cause: anyhow::Error, +} + +trait SupplyKeypairErrorExt { + fn e_custom(self, status: u128) -> Result; + fn einternal(self) -> Result; + fn ealready_supplied(self) -> Result; + fn einvalid_req(self) -> Result; +} + +impl> SupplyKeypairErrorExt for Result { + fn e_custom(self, status: u128) -> Result { + self.map_err(|e| SupplyKeypairError { + status, + cause: e.into(), + }) + } + + fn einternal(self) -> Result { + self.e_custom(supply_keypair_response_status::INTERNAL_ERROR) + } + + fn ealready_supplied(self) -> Result { + self.e_custom(supply_keypair_response_status::KEYPAIR_ALREADY_SUPPLIED) + } + + fn einvalid_req(self) -> Result { + self.e_custom(supply_keypair_response_status::INVALID_REQUEST) + } +} + impl ApiServer for T where T: ?Sized + ApiHandlerContext, @@ -39,4 +77,98 @@ where copy_slice(&req.echo).to(&mut res.echo); Ok(()) } + + fn supply_keypair( + &mut self, + req: &super::SupplyKeypairRequest, + req_fds: &mut VecDeque, + res: &mut super::SupplyKeypairResponse, + ) -> anyhow::Result<()> { + let outcome: Result<(), SupplyKeypairError> = run(|| { + // Acquire the file descriptors + let mut sk_io = FdIo( + req_fds + .front() + .context("First file descriptor, secret key, missing.") + .einvalid_req()?, + ); + let mut pk_io = FdIo( + req_fds + .get(1) + .context("Second file descriptor, public key, missing.") + .einvalid_req()?, + ); + + // Actually read the secrets + let mut sk = crate::protocol::SSk::zero(); + sk_io.read_exact_til_end(sk.secret_mut()).einvalid_req()?; + + let mut pk = crate::protocol::SPk::zero(); + pk_io.read_exact_til_end(pk.borrow_mut()).einvalid_req()?; + + // Retrieve the construction site + let construction_site = self.app_server_mut().crypto_site.borrow_mut(); + + // Retrieve the builder + use rosenpass_util::build::ConstructionSite as C; + let maybe_builder = match construction_site { + C::Builder(builder) => Some(builder), + C::Product(_) => None, + C::Void => { + return Err(anyhow::Error::msg("CryptoServer construction side is void")) + .einternal(); + } + }; + + // Retrieve a reference to the keypair + let Some(BuildCryptoServer { + ref mut keypair, .. + }) = maybe_builder + else { + return Err(anyhow::Error::msg("CryptoServer already built")).ealready_supplied(); + }; + + // Supply the keypair to the CryptoServer + keypair + .insert(crate::protocol::Keypair { sk, pk }) + .discard_result(); + + // Actually construct the CryptoServer + construction_site + .erect() + .map_err(|e| anyhow::Error::msg(format!("Error erecting the CryptoServer {e:?}"))) + .einternal()?; + + Ok(()) + }); + + // Handle errors + use supply_keypair_response_status as status; + let status = match outcome { + Ok(()) => status::OK, + Err(e) => { + let lvl = match e.status { + status::INTERNAL_ERROR => log::Level::Warn, + _ => log::Level::Debug, + }; + + log::log!( + lvl, + "Error while processing API Request.\n Request: {:?}\n Error: {:?}", + req, + e.cause + ); + + if e.status == status::INTERNAL_ERROR { + return Err(e.cause); + } + + e.status + } + }; + + res.payload.status = status; + + Ok(()) + } } diff --git a/rosenpass/src/api/boilerplate/byte_slice_ext.rs b/rosenpass/src/api/boilerplate/byte_slice_ext.rs index dd20b49..93af764 100644 --- a/rosenpass/src/api/boilerplate/byte_slice_ext.rs +++ b/rosenpass/src/api/boilerplate/byte_slice_ext.rs @@ -4,7 +4,7 @@ use rosenpass_util::zerocopy::{RefMaker, ZerocopySliceExt}; use super::{ PingRequest, PingResponse, RawMsgType, RefMakerRawMsgTypeExt, RequestMsgType, RequestRef, - ResponseMsgType, ResponseRef, + ResponseMsgType, ResponseRef, SupplyKeypairRequest, SupplyKeypairResponse, }; pub trait ByteSliceRefExt: ByteSlice { @@ -111,6 +111,38 @@ pub trait ByteSliceRefExt: ByteSlice { fn ping_response_from_suffix(self) -> anyhow::Result> { self.zk_parse_suffix() } + + fn supply_keypair_request(self) -> anyhow::Result> { + self.zk_parse() + } + + fn supply_keypair_request_from_prefix(self) -> anyhow::Result> { + self.zk_parse_prefix() + } + + fn supply_keypair_request_from_suffix(self) -> anyhow::Result> { + self.zk_parse_suffix() + } + + fn supply_keypair_response_maker(self) -> RefMaker { + self.zk_ref_maker() + } + + fn supply_keypair_response(self) -> anyhow::Result> { + self.zk_parse() + } + + fn supply_keypair_response_from_prefix( + self, + ) -> anyhow::Result> { + self.zk_parse_prefix() + } + + fn supply_keypair_response_from_suffix( + self, + ) -> anyhow::Result> { + self.zk_parse_suffix() + } } impl ByteSliceRefExt for B {} diff --git a/rosenpass/src/api/boilerplate/message_type.rs b/rosenpass/src/api/boilerplate/message_type.rs index 3a69564..a867344 100644 --- a/rosenpass/src/api/boilerplate/message_type.rs +++ b/rosenpass/src/api/boilerplate/message_type.rs @@ -14,6 +14,13 @@ pub const PING_REQUEST: RawMsgType = pub const PING_RESPONSE: RawMsgType = RawMsgType::from_le_bytes(hex!("4ec7 f6f0 2bbc ba64 48f1 da14 c7cf 0260")); +// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Supply Keypair Request +const SUPPLY_KEYPAIR_REQUEST: RawMsgType = + RawMsgType::from_le_bytes(hex!("ac91 a5a6 4f4b 21d0 ac7f 9b55 74f7 3529")); +// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Supply Keypair Response +const SUPPLY_KEYPAIR_RESPONSE: RawMsgType = + RawMsgType::from_le_bytes(hex!("f2dc 49bd e261 5f10 40b7 3c16 ec61 edb9")); + pub trait MessageAttributes { fn message_size(&self) -> usize; } @@ -21,17 +28,20 @@ pub trait MessageAttributes { #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] pub enum RequestMsgType { Ping, + SupplyKeypair, } #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] pub enum ResponseMsgType { Ping, + SupplyKeypair, } impl MessageAttributes for RequestMsgType { fn message_size(&self) -> usize { match self { Self::Ping => std::mem::size_of::(), + Self::SupplyKeypair => std::mem::size_of::(), } } } @@ -40,6 +50,7 @@ impl MessageAttributes for ResponseMsgType { fn message_size(&self) -> usize { match self { Self::Ping => std::mem::size_of::(), + Self::SupplyKeypair => std::mem::size_of::(), } } } @@ -51,6 +62,7 @@ impl TryFrom for RequestMsgType { use RequestMsgType as E; Ok(match value { self::PING_REQUEST => E::Ping, + self::SUPPLY_KEYPAIR_REQUEST => E::SupplyKeypair, _ => return Err(InvalidApiMessageType(value)), }) } @@ -61,6 +73,7 @@ impl From for RawMsgType { use RequestMsgType as E; match val { E::Ping => self::PING_REQUEST, + E::SupplyKeypair => self::SUPPLY_KEYPAIR_REQUEST, } } } @@ -72,6 +85,7 @@ impl TryFrom for ResponseMsgType { use ResponseMsgType as E; Ok(match value { self::PING_RESPONSE => E::Ping, + self::SUPPLY_KEYPAIR_RESPONSE => E::SupplyKeypair, _ => return Err(InvalidApiMessageType(value)), }) } @@ -82,6 +96,7 @@ impl From for RawMsgType { use ResponseMsgType as E; match val { E::Ping => self::PING_RESPONSE, + E::SupplyKeypair => self::SUPPLY_KEYPAIR_RESPONSE, } } } diff --git a/rosenpass/src/api/boilerplate/payload.rs b/rosenpass/src/api/boilerplate/payload.rs index ea67939..cbf6332 100644 --- a/rosenpass/src/api/boilerplate/payload.rs +++ b/rosenpass/src/api/boilerplate/payload.rs @@ -95,3 +95,89 @@ impl Message for PingResponse { self.msg_type = Self::MESSAGE_TYPE.into(); } } + +#[repr(packed)] +#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)] +pub struct SupplyKeypairRequestPayload {} + +pub type SupplyKeypairRequest = RequestEnvelope; + +impl Default for SupplyKeypairRequest { + fn default() -> Self { + Self::new() + } +} + +impl SupplyKeypairRequest { + pub fn new() -> Self { + Self::from_payload(SupplyKeypairRequestPayload {}) + } +} + +impl Message for SupplyKeypairRequest { + type Payload = SupplyKeypairRequestPayload; + type MessageClass = RequestMsgType; + const MESSAGE_TYPE: Self::MessageClass = RequestMsgType::SupplyKeypair; + + 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(); + } +} + +pub mod supply_keypair_response_status { + pub const OK: u128 = 0; + pub const KEYPAIR_ALREADY_SUPPLIED: u128 = 1; + pub const INTERNAL_ERROR: u128 = 2; + pub const INVALID_REQUEST: u128 = 3; + pub const IO_ERROR: u128 = 4; +} + +#[repr(packed)] +#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)] +pub struct SupplyKeypairResponsePayload { + pub status: u128, +} + +pub type SupplyKeypairResponse = ResponseEnvelope; + +impl SupplyKeypairResponse { + pub fn new(status: u128) -> Self { + Self::from_payload(SupplyKeypairResponsePayload { status }) + } +} + +impl Message for SupplyKeypairResponse { + type Payload = SupplyKeypairResponsePayload; + type MessageClass = ResponseMsgType; + const MESSAGE_TYPE: Self::MessageClass = ResponseMsgType::SupplyKeypair; + + 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 index 22f6bd1..198342e 100644 --- a/rosenpass/src/api/boilerplate/request_ref.rs +++ b/rosenpass/src/api/boilerplate/request_ref.rs @@ -25,6 +25,7 @@ impl RequestRef { pub fn message_type(&self) -> RequestMsgType { match self { Self::Ping(_) => RequestMsgType::Ping, + Self::SupplyKeypair(_) => RequestMsgType::SupplyKeypair, } } } @@ -48,6 +49,9 @@ impl RequestRefMaker { fn parse(self) -> anyhow::Result> { Ok(match self.msg_type { RequestMsgType::Ping => RequestRef::Ping(self.buf.ping_request()?), + RequestMsgType::SupplyKeypair => { + RequestRef::SupplyKeypair(self.buf.supply_keypair_request()?) + } }) } @@ -82,6 +86,7 @@ impl RequestRefMaker { pub enum RequestRef { Ping(Ref), + SupplyKeypair(Ref), } impl RequestRef @@ -91,6 +96,7 @@ where pub fn bytes(&self) -> &[u8] { match self { Self::Ping(r) => r.bytes(), + Self::SupplyKeypair(r) => r.bytes(), } } } @@ -102,6 +108,7 @@ where pub fn bytes_mut(&mut self) -> &[u8] { match self { Self::Ping(r) => r.bytes_mut(), + Self::SupplyKeypair(r) => r.bytes_mut(), } } } diff --git a/rosenpass/src/api/boilerplate/request_response.rs b/rosenpass/src/api/boilerplate/request_response.rs index 5af7a20..69e0d34 100644 --- a/rosenpass/src/api/boilerplate/request_response.rs +++ b/rosenpass/src/api/boilerplate/request_response.rs @@ -42,10 +42,23 @@ impl ResponseMsg for PingResponse { type RequestMsg = PingRequest; } +impl RequestMsg for super::SupplyKeypairRequest { + type ResponseMsg = super::SupplyKeypairResponse; +} + +impl ResponseMsg for super::SupplyKeypairResponse { + type RequestMsg = super::SupplyKeypairRequest; +} + pub type PingPair = (Ref, Ref); +pub type SupplyKeypairPair = ( + Ref, + Ref, +); pub enum RequestResponsePair { Ping(PingPair), + SupplyKeypair(SupplyKeypairPair), } impl From> for RequestResponsePair { @@ -54,6 +67,12 @@ impl From> for RequestResponsePair { } } +impl From> for RequestResponsePair { + fn from(v: SupplyKeypairPair) -> Self { + RequestResponsePair::SupplyKeypair(v) + } +} + impl RequestResponsePair where B1: ByteSlice, @@ -66,6 +85,11 @@ where let res = ResponseRef::Ping(res.emancipate()); (req, res) } + Self::SupplyKeypair((req, res)) => { + let req = RequestRef::SupplyKeypair(req.emancipate()); + let res = ResponseRef::SupplyKeypair(res.emancipate()); + (req, res) + } } } @@ -90,6 +114,11 @@ where let res = ResponseRef::Ping(res.emancipate_mut()); (req, res) } + Self::SupplyKeypair((req, res)) => { + let req = RequestRef::SupplyKeypair(req.emancipate_mut()); + let res = ResponseRef::SupplyKeypair(res.emancipate_mut()); + (req, res) + } } } diff --git a/rosenpass/src/api/boilerplate/response_ref.rs b/rosenpass/src/api/boilerplate/response_ref.rs index 38b1e75..d028685 100644 --- a/rosenpass/src/api/boilerplate/response_ref.rs +++ b/rosenpass/src/api/boilerplate/response_ref.rs @@ -26,6 +26,7 @@ impl ResponseRef { pub fn message_type(&self) -> ResponseMsgType { match self { Self::Ping(_) => ResponseMsgType::Ping, + Self::SupplyKeypair(_) => ResponseMsgType::SupplyKeypair, } } } @@ -36,6 +37,12 @@ impl From> for ResponseRef { } } +impl From> for ResponseRef { + fn from(v: Ref) -> Self { + Self::SupplyKeypair(v) + } +} + impl ResponseRefMaker { fn new(buf: B) -> anyhow::Result { let msg_type = buf.deref().response_msg_type_from_prefix()?; @@ -49,6 +56,9 @@ impl ResponseRefMaker { fn parse(self) -> anyhow::Result> { Ok(match self.msg_type { ResponseMsgType::Ping => ResponseRef::Ping(self.buf.ping_response()?), + ResponseMsgType::SupplyKeypair => { + ResponseRef::SupplyKeypair(self.buf.supply_keypair_response()?) + } }) } @@ -83,6 +93,7 @@ impl ResponseRefMaker { pub enum ResponseRef { Ping(Ref), + SupplyKeypair(Ref), } impl ResponseRef @@ -92,6 +103,7 @@ where pub fn bytes(&self) -> &[u8] { match self { Self::Ping(r) => r.bytes(), + Self::SupplyKeypair(r) => r.bytes(), } } } @@ -103,6 +115,7 @@ where pub fn bytes_mut(&mut self) -> &[u8] { match self { Self::Ping(r) => r.bytes_mut(), + Self::SupplyKeypair(r) => r.bytes_mut(), } } } diff --git a/rosenpass/src/api/boilerplate/server.rs b/rosenpass/src/api/boilerplate/server.rs index a89ea03..92aefea 100644 --- a/rosenpass/src/api/boilerplate/server.rs +++ b/rosenpass/src/api/boilerplate/server.rs @@ -10,6 +10,13 @@ pub trait Server { res: &mut PingResponse, ) -> anyhow::Result<()>; + fn supply_keypair( + &mut self, + req: &super::SupplyKeypairRequest, + req_fds: &mut VecDeque, + res: &mut super::SupplyKeypairResponse, + ) -> anyhow::Result<()>; + fn dispatch( &mut self, p: &mut RequestResponsePair, @@ -21,6 +28,9 @@ pub trait Server { { match p { RequestResponsePair::Ping((req, res)) => self.ping(req, req_fds, res), + RequestResponsePair::SupplyKeypair((req, res)) => { + self.supply_keypair(req, req_fds, res) + } } } @@ -42,6 +52,11 @@ pub trait Server { res.init(); RequestResponsePair::Ping((req, res)) } + RequestRef::SupplyKeypair(req) => { + let mut res = res.supply_keypair_response_from_prefix()?; + res.init(); + RequestResponsePair::SupplyKeypair((req, res)) + } }; self.dispatch(&mut pair, req_fds)?; diff --git a/rosenpass/src/bin/gen-ipc-msg-types.rs b/rosenpass/src/bin/gen-ipc-msg-types.rs index 2e128de..9d36801 100644 --- a/rosenpass/src/bin/gen-ipc-msg-types.rs +++ b/rosenpass/src/bin/gen-ipc-msg-types.rs @@ -76,6 +76,8 @@ fn main() -> Result<()> { vec![ Tree::Leaf("Ping Request".to_owned()), Tree::Leaf("Ping Response".to_owned()), + Tree::Leaf("Supply Keypair Request".to_owned()), + Tree::Leaf("Supply Keypair Response".to_owned()), ], )], ); diff --git a/rosenpass/tests/api-integration-tests-supply-keypair.rs b/rosenpass/tests/api-integration-tests-supply-keypair.rs new file mode 100644 index 0000000..da25391 --- /dev/null +++ b/rosenpass/tests/api-integration-tests-supply-keypair.rs @@ -0,0 +1,205 @@ +use std::{ + io::{BufRead, BufReader}, + net::ToSocketAddrs, + os::unix::net::UnixStream, + process::Stdio, + thread::sleep, + time::Duration, +}; + +use anyhow::{bail, Context}; +use rosenpass::api::{self, supply_keypair_response_status}; +use rosenpass_util::{ + file::LoadValueB64, + length_prefix_encoding::{decoder::LengthPrefixDecoder, encoder::LengthPrefixEncoder}, +}; +use rosenpass_util::{mio::WriteWithFileDescriptors, zerocopy::ZerocopySliceExt}; +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]:61424"; + let peer_a_osk = tempfile!("a.osk"); + let peer_b_osk = tempfile!("b.osk"); + + use rosenpass::config; + + let peer_a_keypair = config::Keypair::new(tempfile!("a.pk"), tempfile!("a.sk")); + let peer_a = config::Rosenpass { + config_file_path: tempfile!("a.config"), + keypair: Some(peer_a_keypair.clone()), + 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_keypair = config::Keypair::new(tempfile!("b.pk"), tempfile!("b.sk")); + let peer_b = config::Rosenpass { + config_file_path: tempfile!("b.config"), + keypair: None, + 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_keypair.secret_key.clone(), + peer_a_keypair.public_key.clone(), + )?; + rosenpass::cli::testing::generate_and_save_keypair( + peer_b_keypair.secret_key.clone(), + peer_b_keypair.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(); + + // Now connect to the peers + let api_path = peer_b.api.listen_path[0].as_path(); + + // Wait for the socket to be created + let attempt = 0; + while !api_path.exists() { + sleep(Duration::from_millis(200)); + assert!( + attempt < 50, + "Api failed to be created even after 50 seconds" + ); + } + + let api = UnixStream::connect(api_path)?; + + // Send SupplyKeypairRequest + { + use rustix::fs::{open, Mode, OFlags}; + let sk = open(peer_b_keypair.secret_key, OFlags::RDONLY, Mode::empty())?; + let pk = open(peer_b_keypair.public_key, OFlags::RDONLY, Mode::empty())?; + + let mut fds = vec![&sk, &pk].into(); + let mut api = WriteWithFileDescriptors::::new(&api, &mut fds); + LengthPrefixEncoder::from_message(api::SupplyKeypairRequest::new().as_bytes()) + .write_all_to_stdio(&mut api)?; + assert!(fds.is_empty(), "Failed to write all file descriptors"); + } + + // Read response + { + //sleep(Duration::from_secs(10)); + let mut decoder = LengthPrefixDecoder::new([0u8; api::MAX_RESPONSE_LEN]); + let res = decoder.read_all_from_stdio(api)?; + let res = res.zk_parse::()?; + assert_eq!( + *res, + api::SupplyKeypairResponse::new(supply_keypair_response_status::OK) + ); + } + + // 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; + } + + Ok(()) +} diff --git a/util/src/io.rs b/util/src/io.rs index 111959f..31ab95f 100644 --- a/util/src/io.rs +++ b/util/src/io.rs @@ -1,5 +1,7 @@ use std::{borrow::Borrow, io}; +use anyhow::ensure; + pub trait IoErrorKind { fn io_error_kind(&self) -> io::ErrorKind; } @@ -108,3 +110,21 @@ impl ReadNonblockingWithBoringErrorsHandledExt for T { nonblocking_handle_io_errors(|| self.read(buf)) } } + +pub trait ReadExt { + fn read_exact_til_end(&mut self, buf: &mut [u8]) -> anyhow::Result<()>; +} + +impl ReadExt for T +where + T: std::io::Read, +{ + fn read_exact_til_end(&mut self, buf: &mut [u8]) -> anyhow::Result<()> { + self.read_exact(buf)?; + ensure!( + self.read(&mut [0u8; 8])? == 0, + "Read source longer than buffer" + ); + Ok(()) + } +} diff --git a/util/src/lib.rs b/util/src/lib.rs index eeaa920..7eb7c6d 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -2,6 +2,7 @@ pub mod b64; pub mod build; +pub mod controlflow; pub mod fd; pub mod file; pub mod functional; diff --git a/util/src/mio/mod.rs b/util/src/mio/mod.rs index 251e211..3b87e6e 100644 --- a/util/src/mio/mod.rs +++ b/util/src/mio/mod.rs @@ -2,11 +2,12 @@ mod mio; pub use mio::*; -#[cfg(feature = "experiment_file_descriptor_passing")] -mod uds_recv_fd; #[cfg(feature = "experiment_file_descriptor_passing")] mod uds_send_fd; #[cfg(feature = "experiment_file_descriptor_passing")] -pub use uds_recv_fd::*; -#[cfg(feature = "experiment_file_descriptor_passing")] pub use uds_send_fd::*; + +#[cfg(feature = "experiment_file_descriptor_passing")] +mod uds_recv_fd; +#[cfg(feature = "experiment_file_descriptor_passing")] +pub use uds_recv_fd::*;