feat(API): SupplyKeypair endpoint

This commit is contained in:
Karolin Varner
2024-08-11 00:47:27 +02:00
parent 7a31b57227
commit edf1e774c1
14 changed files with 570 additions and 7 deletions

View File

@@ -22,6 +22,10 @@ required-features = ["experiment_api", "internal_bin_gen_ipc_msg_types"]
name = "api-integration-tests" name = "api-integration-tests"
required-features = ["experiment_api", "internal_testing"] required-features = ["experiment_api", "internal_testing"]
[[test]]
name = "api-integration-tests-supply-keypair"
required-features = ["experiment_api", "internal_testing"]
[[bench]] [[bench]]
name = "handshake" name = "handshake"
harness = false harness = false
@@ -67,6 +71,7 @@ stacker = { workspace = true }
serial_test = {workspace = true} serial_test = {workspace = true}
procspawn = {workspace = true} procspawn = {workspace = true}
tempfile = { workspace = true } tempfile = { workspace = true }
rustix = {workspace = true}
[features] [features]
experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"] experiment_memfd_secret = ["rosenpass-wireguard-broker/experiment_memfd_secret"]

View File

@@ -1,10 +1,12 @@
use std::{borrow::BorrowMut, collections::VecDeque, os::fd::OwnedFd}; use std::{borrow::BorrowMut, collections::VecDeque, os::fd::OwnedFd};
use anyhow::Context;
use rosenpass_to::{ops::copy_slice, To}; 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)] #[derive(Debug)]
pub struct ApiHandler { pub struct ApiHandler {
@@ -25,6 +27,42 @@ pub trait ApiHandlerContext {
fn app_server_mut(&mut self) -> &mut AppServer; 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<T> {
fn e_custom(self, status: u128) -> Result<T, SupplyKeypairError>;
fn einternal(self) -> Result<T, SupplyKeypairError>;
fn ealready_supplied(self) -> Result<T, SupplyKeypairError>;
fn einvalid_req(self) -> Result<T, SupplyKeypairError>;
}
impl<T, E: Into<anyhow::Error>> SupplyKeypairErrorExt<T> for Result<T, E> {
fn e_custom(self, status: u128) -> Result<T, SupplyKeypairError> {
self.map_err(|e| SupplyKeypairError {
status,
cause: e.into(),
})
}
fn einternal(self) -> Result<T, SupplyKeypairError> {
self.e_custom(supply_keypair_response_status::INTERNAL_ERROR)
}
fn ealready_supplied(self) -> Result<T, SupplyKeypairError> {
self.e_custom(supply_keypair_response_status::KEYPAIR_ALREADY_SUPPLIED)
}
fn einvalid_req(self) -> Result<T, SupplyKeypairError> {
self.e_custom(supply_keypair_response_status::INVALID_REQUEST)
}
}
impl<T> ApiServer for T impl<T> ApiServer for T
where where
T: ?Sized + ApiHandlerContext, T: ?Sized + ApiHandlerContext,
@@ -39,4 +77,98 @@ where
copy_slice(&req.echo).to(&mut res.echo); copy_slice(&req.echo).to(&mut res.echo);
Ok(()) Ok(())
} }
fn supply_keypair(
&mut self,
req: &super::SupplyKeypairRequest,
req_fds: &mut VecDeque<OwnedFd>,
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(())
}
} }

View File

@@ -4,7 +4,7 @@ use rosenpass_util::zerocopy::{RefMaker, ZerocopySliceExt};
use super::{ use super::{
PingRequest, PingResponse, RawMsgType, RefMakerRawMsgTypeExt, RequestMsgType, RequestRef, PingRequest, PingResponse, RawMsgType, RefMakerRawMsgTypeExt, RequestMsgType, RequestRef,
ResponseMsgType, ResponseRef, ResponseMsgType, ResponseRef, SupplyKeypairRequest, SupplyKeypairResponse,
}; };
pub trait ByteSliceRefExt: ByteSlice { pub trait ByteSliceRefExt: ByteSlice {
@@ -111,6 +111,38 @@ pub trait ByteSliceRefExt: ByteSlice {
fn ping_response_from_suffix(self) -> anyhow::Result<Ref<Self, PingResponse>> { fn ping_response_from_suffix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
self.zk_parse_suffix() self.zk_parse_suffix()
} }
fn supply_keypair_request(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
self.zk_parse()
}
fn supply_keypair_request_from_prefix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
self.zk_parse_prefix()
}
fn supply_keypair_request_from_suffix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
self.zk_parse_suffix()
}
fn supply_keypair_response_maker(self) -> RefMaker<Self, SupplyKeypairResponse> {
self.zk_ref_maker()
}
fn supply_keypair_response(self) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
self.zk_parse()
}
fn supply_keypair_response_from_prefix(
self,
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
self.zk_parse_prefix()
}
fn supply_keypair_response_from_suffix(
self,
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
self.zk_parse_suffix()
}
} }
impl<B: ByteSlice> ByteSliceRefExt for B {} impl<B: ByteSlice> ByteSliceRefExt for B {}

View File

@@ -14,6 +14,13 @@ pub const PING_REQUEST: RawMsgType =
pub const PING_RESPONSE: RawMsgType = pub const PING_RESPONSE: RawMsgType =
RawMsgType::from_le_bytes(hex!("4ec7 f6f0 2bbc ba64 48f1 da14 c7cf 0260")); 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 { pub trait MessageAttributes {
fn message_size(&self) -> usize; fn message_size(&self) -> usize;
} }
@@ -21,17 +28,20 @@ pub trait MessageAttributes {
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum RequestMsgType { pub enum RequestMsgType {
Ping, Ping,
SupplyKeypair,
} }
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum ResponseMsgType { pub enum ResponseMsgType {
Ping, Ping,
SupplyKeypair,
} }
impl MessageAttributes for RequestMsgType { impl MessageAttributes for RequestMsgType {
fn message_size(&self) -> usize { fn message_size(&self) -> usize {
match self { match self {
Self::Ping => std::mem::size_of::<super::PingRequest>(), Self::Ping => std::mem::size_of::<super::PingRequest>(),
Self::SupplyKeypair => std::mem::size_of::<super::SupplyKeypairRequest>(),
} }
} }
} }
@@ -40,6 +50,7 @@ impl MessageAttributes for ResponseMsgType {
fn message_size(&self) -> usize { fn message_size(&self) -> usize {
match self { match self {
Self::Ping => std::mem::size_of::<super::PingResponse>(), Self::Ping => std::mem::size_of::<super::PingResponse>(),
Self::SupplyKeypair => std::mem::size_of::<super::SupplyKeypairResponse>(),
} }
} }
} }
@@ -51,6 +62,7 @@ impl TryFrom<RawMsgType> for RequestMsgType {
use RequestMsgType as E; use RequestMsgType as E;
Ok(match value { Ok(match value {
self::PING_REQUEST => E::Ping, self::PING_REQUEST => E::Ping,
self::SUPPLY_KEYPAIR_REQUEST => E::SupplyKeypair,
_ => return Err(InvalidApiMessageType(value)), _ => return Err(InvalidApiMessageType(value)),
}) })
} }
@@ -61,6 +73,7 @@ impl From<RequestMsgType> for RawMsgType {
use RequestMsgType as E; use RequestMsgType as E;
match val { match val {
E::Ping => self::PING_REQUEST, E::Ping => self::PING_REQUEST,
E::SupplyKeypair => self::SUPPLY_KEYPAIR_REQUEST,
} }
} }
} }
@@ -72,6 +85,7 @@ impl TryFrom<RawMsgType> for ResponseMsgType {
use ResponseMsgType as E; use ResponseMsgType as E;
Ok(match value { Ok(match value {
self::PING_RESPONSE => E::Ping, self::PING_RESPONSE => E::Ping,
self::SUPPLY_KEYPAIR_RESPONSE => E::SupplyKeypair,
_ => return Err(InvalidApiMessageType(value)), _ => return Err(InvalidApiMessageType(value)),
}) })
} }
@@ -82,6 +96,7 @@ impl From<ResponseMsgType> for RawMsgType {
use ResponseMsgType as E; use ResponseMsgType as E;
match val { match val {
E::Ping => self::PING_RESPONSE, E::Ping => self::PING_RESPONSE,
E::SupplyKeypair => self::SUPPLY_KEYPAIR_RESPONSE,
} }
} }
} }

View File

@@ -95,3 +95,89 @@ impl Message for PingResponse {
self.msg_type = Self::MESSAGE_TYPE.into(); 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<SupplyKeypairRequestPayload>;
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<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
let mut r: Ref<B, Self> = 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<SupplyKeypairResponsePayload>;
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<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>> {
let mut r: Ref<B, Self> = buf.zk_zeroized()?;
r.init();
Ok(r)
}
fn init(&mut self) {
self.msg_type = Self::MESSAGE_TYPE.into();
}
}

View File

@@ -25,6 +25,7 @@ impl<B: ByteSlice> RequestRef<B> {
pub fn message_type(&self) -> RequestMsgType { pub fn message_type(&self) -> RequestMsgType {
match self { match self {
Self::Ping(_) => RequestMsgType::Ping, Self::Ping(_) => RequestMsgType::Ping,
Self::SupplyKeypair(_) => RequestMsgType::SupplyKeypair,
} }
} }
} }
@@ -48,6 +49,9 @@ impl<B: ByteSlice> RequestRefMaker<B> {
fn parse(self) -> anyhow::Result<RequestRef<B>> { fn parse(self) -> anyhow::Result<RequestRef<B>> {
Ok(match self.msg_type { Ok(match self.msg_type {
RequestMsgType::Ping => RequestRef::Ping(self.buf.ping_request()?), RequestMsgType::Ping => RequestRef::Ping(self.buf.ping_request()?),
RequestMsgType::SupplyKeypair => {
RequestRef::SupplyKeypair(self.buf.supply_keypair_request()?)
}
}) })
} }
@@ -82,6 +86,7 @@ impl<B: ByteSlice> RequestRefMaker<B> {
pub enum RequestRef<B> { pub enum RequestRef<B> {
Ping(Ref<B, PingRequest>), Ping(Ref<B, PingRequest>),
SupplyKeypair(Ref<B, super::SupplyKeypairRequest>),
} }
impl<B> RequestRef<B> impl<B> RequestRef<B>
@@ -91,6 +96,7 @@ where
pub fn bytes(&self) -> &[u8] { pub fn bytes(&self) -> &[u8] {
match self { match self {
Self::Ping(r) => r.bytes(), Self::Ping(r) => r.bytes(),
Self::SupplyKeypair(r) => r.bytes(),
} }
} }
} }
@@ -102,6 +108,7 @@ where
pub fn bytes_mut(&mut self) -> &[u8] { pub fn bytes_mut(&mut self) -> &[u8] {
match self { match self {
Self::Ping(r) => r.bytes_mut(), Self::Ping(r) => r.bytes_mut(),
Self::SupplyKeypair(r) => r.bytes_mut(),
} }
} }
} }

View File

@@ -42,10 +42,23 @@ impl ResponseMsg for PingResponse {
type RequestMsg = PingRequest; type RequestMsg = PingRequest;
} }
impl RequestMsg for super::SupplyKeypairRequest {
type ResponseMsg = super::SupplyKeypairResponse;
}
impl ResponseMsg for super::SupplyKeypairResponse {
type RequestMsg = super::SupplyKeypairRequest;
}
pub type PingPair<B1, B2> = (Ref<B1, PingRequest>, Ref<B2, PingResponse>); pub type PingPair<B1, B2> = (Ref<B1, PingRequest>, Ref<B2, PingResponse>);
pub type SupplyKeypairPair<B1, B2> = (
Ref<B1, super::SupplyKeypairRequest>,
Ref<B2, super::SupplyKeypairResponse>,
);
pub enum RequestResponsePair<B1, B2> { pub enum RequestResponsePair<B1, B2> {
Ping(PingPair<B1, B2>), Ping(PingPair<B1, B2>),
SupplyKeypair(SupplyKeypairPair<B1, B2>),
} }
impl<B1, B2> From<PingPair<B1, B2>> for RequestResponsePair<B1, B2> { impl<B1, B2> From<PingPair<B1, B2>> for RequestResponsePair<B1, B2> {
@@ -54,6 +67,12 @@ impl<B1, B2> From<PingPair<B1, B2>> for RequestResponsePair<B1, B2> {
} }
} }
impl<B1, B2> From<SupplyKeypairPair<B1, B2>> for RequestResponsePair<B1, B2> {
fn from(v: SupplyKeypairPair<B1, B2>) -> Self {
RequestResponsePair::SupplyKeypair(v)
}
}
impl<B1, B2> RequestResponsePair<B1, B2> impl<B1, B2> RequestResponsePair<B1, B2>
where where
B1: ByteSlice, B1: ByteSlice,
@@ -66,6 +85,11 @@ where
let res = ResponseRef::Ping(res.emancipate()); let res = ResponseRef::Ping(res.emancipate());
(req, res) (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()); let res = ResponseRef::Ping(res.emancipate_mut());
(req, res) (req, res)
} }
Self::SupplyKeypair((req, res)) => {
let req = RequestRef::SupplyKeypair(req.emancipate_mut());
let res = ResponseRef::SupplyKeypair(res.emancipate_mut());
(req, res)
}
} }
} }

View File

@@ -26,6 +26,7 @@ impl<B: ByteSlice> ResponseRef<B> {
pub fn message_type(&self) -> ResponseMsgType { pub fn message_type(&self) -> ResponseMsgType {
match self { match self {
Self::Ping(_) => ResponseMsgType::Ping, Self::Ping(_) => ResponseMsgType::Ping,
Self::SupplyKeypair(_) => ResponseMsgType::SupplyKeypair,
} }
} }
} }
@@ -36,6 +37,12 @@ impl<B> From<Ref<B, PingResponse>> for ResponseRef<B> {
} }
} }
impl<B> From<Ref<B, super::SupplyKeypairResponse>> for ResponseRef<B> {
fn from(v: Ref<B, super::SupplyKeypairResponse>) -> Self {
Self::SupplyKeypair(v)
}
}
impl<B: ByteSlice> ResponseRefMaker<B> { impl<B: ByteSlice> ResponseRefMaker<B> {
fn new(buf: B) -> anyhow::Result<Self> { fn new(buf: B) -> anyhow::Result<Self> {
let msg_type = buf.deref().response_msg_type_from_prefix()?; let msg_type = buf.deref().response_msg_type_from_prefix()?;
@@ -49,6 +56,9 @@ impl<B: ByteSlice> ResponseRefMaker<B> {
fn parse(self) -> anyhow::Result<ResponseRef<B>> { fn parse(self) -> anyhow::Result<ResponseRef<B>> {
Ok(match self.msg_type { Ok(match self.msg_type {
ResponseMsgType::Ping => ResponseRef::Ping(self.buf.ping_response()?), ResponseMsgType::Ping => ResponseRef::Ping(self.buf.ping_response()?),
ResponseMsgType::SupplyKeypair => {
ResponseRef::SupplyKeypair(self.buf.supply_keypair_response()?)
}
}) })
} }
@@ -83,6 +93,7 @@ impl<B: ByteSlice> ResponseRefMaker<B> {
pub enum ResponseRef<B> { pub enum ResponseRef<B> {
Ping(Ref<B, PingResponse>), Ping(Ref<B, PingResponse>),
SupplyKeypair(Ref<B, super::SupplyKeypairResponse>),
} }
impl<B> ResponseRef<B> impl<B> ResponseRef<B>
@@ -92,6 +103,7 @@ where
pub fn bytes(&self) -> &[u8] { pub fn bytes(&self) -> &[u8] {
match self { match self {
Self::Ping(r) => r.bytes(), Self::Ping(r) => r.bytes(),
Self::SupplyKeypair(r) => r.bytes(),
} }
} }
} }
@@ -103,6 +115,7 @@ where
pub fn bytes_mut(&mut self) -> &[u8] { pub fn bytes_mut(&mut self) -> &[u8] {
match self { match self {
Self::Ping(r) => r.bytes_mut(), Self::Ping(r) => r.bytes_mut(),
Self::SupplyKeypair(r) => r.bytes_mut(),
} }
} }
} }

View File

@@ -10,6 +10,13 @@ pub trait Server {
res: &mut PingResponse, res: &mut PingResponse,
) -> anyhow::Result<()>; ) -> anyhow::Result<()>;
fn supply_keypair(
&mut self,
req: &super::SupplyKeypairRequest,
req_fds: &mut VecDeque<OwnedFd>,
res: &mut super::SupplyKeypairResponse,
) -> anyhow::Result<()>;
fn dispatch<ReqBuf, ResBuf>( fn dispatch<ReqBuf, ResBuf>(
&mut self, &mut self,
p: &mut RequestResponsePair<ReqBuf, ResBuf>, p: &mut RequestResponsePair<ReqBuf, ResBuf>,
@@ -21,6 +28,9 @@ pub trait Server {
{ {
match p { match p {
RequestResponsePair::Ping((req, res)) => self.ping(req, req_fds, res), 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(); res.init();
RequestResponsePair::Ping((req, res)) 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)?; self.dispatch(&mut pair, req_fds)?;

View File

@@ -76,6 +76,8 @@ fn main() -> Result<()> {
vec![ vec![
Tree::Leaf("Ping Request".to_owned()), Tree::Leaf("Ping Request".to_owned()),
Tree::Leaf("Ping Response".to_owned()), Tree::Leaf("Ping Response".to_owned()),
Tree::Leaf("Supply Keypair Request".to_owned()),
Tree::Leaf("Supply Keypair Response".to_owned()),
], ],
)], )],
); );

View File

@@ -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::<UnixStream, _, _, _>::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::<api::SupplyKeypairResponse>()?;
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::<Vec<_>>();
let words_b = line_b.split(' ').collect::<Vec<_>>();
// 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(())
}

View File

@@ -1,5 +1,7 @@
use std::{borrow::Borrow, io}; use std::{borrow::Borrow, io};
use anyhow::ensure;
pub trait IoErrorKind { pub trait IoErrorKind {
fn io_error_kind(&self) -> io::ErrorKind; fn io_error_kind(&self) -> io::ErrorKind;
} }
@@ -108,3 +110,21 @@ impl<T: io::Read> ReadNonblockingWithBoringErrorsHandledExt for T {
nonblocking_handle_io_errors(|| self.read(buf)) nonblocking_handle_io_errors(|| self.read(buf))
} }
} }
pub trait ReadExt {
fn read_exact_til_end(&mut self, buf: &mut [u8]) -> anyhow::Result<()>;
}
impl<T> 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(())
}
}

View File

@@ -2,6 +2,7 @@
pub mod b64; pub mod b64;
pub mod build; pub mod build;
pub mod controlflow;
pub mod fd; pub mod fd;
pub mod file; pub mod file;
pub mod functional; pub mod functional;

View File

@@ -2,11 +2,12 @@
mod mio; mod mio;
pub use mio::*; pub use mio::*;
#[cfg(feature = "experiment_file_descriptor_passing")]
mod uds_recv_fd;
#[cfg(feature = "experiment_file_descriptor_passing")] #[cfg(feature = "experiment_file_descriptor_passing")]
mod uds_send_fd; mod uds_send_fd;
#[cfg(feature = "experiment_file_descriptor_passing")] #[cfg(feature = "experiment_file_descriptor_passing")]
pub use uds_recv_fd::*;
#[cfg(feature = "experiment_file_descriptor_passing")]
pub use uds_send_fd::*; 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::*;