Compare commits

..

4 Commits

Author SHA1 Message Date
Karolin Varner
caf91c84f0 chore: Remove unused warning in api integration test 2024-12-18 13:45:38 +01:00
Karolin Varner
f5b4c17011 fix: Disable asserts that rely on timing characteristics during coverage testing 2024-12-18 13:45:38 +01:00
Karolin Varner
12506e5f95 chore: Final improvements on the to crate API doc 2024-12-18 13:45:38 +01:00
David Niehues
965600212d docs+doctest(to): Add tests, examples and documentation to the to-crate 2024-12-18 13:45:38 +01:00
58 changed files with 200 additions and 3395 deletions

12
Cargo.lock generated
View File

@@ -109,9 +109,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.95"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
dependencies = [
"backtrace",
]
@@ -2114,18 +2114,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.217"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -50,9 +50,9 @@ log = { version = "0.4.22" }
clap = { version = "4.5.23", features = ["derive"] }
clap_mangen = "0.2.24"
clap_complete = "4.5.40"
serde = { version = "1.0.217", features = ["derive"] }
serde = { version = "1.0.216", features = ["derive"] }
arbitrary = { version = "1.4.1", features = ["derive"] }
anyhow = { version = "1.0.95", features = ["backtrace", "std"] }
anyhow = { version = "1.0.94", features = ["backtrace", "std"] }
mio = { version = "1.0.3", features = ["net", "os-poll"] }
oqs-sys = { version = "0.9.1", default-features = false, features = [
'classic_mceliece',

View File

@@ -23,8 +23,7 @@ main() {
exc cargo llvm-cov --all-features --workspace --doctests --branch
exc rm -rf target/llvm-cov-target/debug/deps/doctestbins
exc mv -v target/llvm-cov-target/doctestbins target/llvm-cov-target/debug/deps/
exc cp -rv target/llvm-cov-target/doctestbins target/llvm-cov-target/debug/deps/doctestbins
exc rm -rf "${OUTPUT_DIR}"
exc mkdir -p "${OUTPUT_DIR}"
exc grcov target/llvm-cov-target/ --llvm -s . --branch \

View File

@@ -50,8 +50,6 @@ rustPlatform.buildRustPackage {
cargoBuildOptions = [ "--package" package ];
cargoTestOptions = [ "--package" package ];
buildFeatures = [ "experiment_api" ];
doCheck = true;
cargoLock = {

View File

@@ -26,10 +26,6 @@ required-features = ["experiment_api", "internal_testing"]
name = "api-integration-tests-api-setup"
required-features = ["experiment_api", "internal_testing"]
[[test]]
name = "gen-ipc-msg-types"
required-features = ["experiment_api", "internal_testing", "internal_bin_gen_ipc_msg_types"]
[[bench]]
name = "handshake"
harness = false

View File

@@ -8,250 +8,210 @@ use super::{
};
pub trait ByteSliceRefExt: ByteSlice {
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn msg_type_maker(self) -> RefMaker<Self, RawMsgType> {
self.zk_ref_maker()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker] and
/// [RefMakerRawMsgTypeExt::parse_request_msg_type]
fn msg_type(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse()
}
fn msg_type_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse_prefix()
}
fn msg_type_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse_suffix()
}
fn request_msg_type(self) -> anyhow::Result<RequestMsgType> {
self.msg_type_maker().parse_request_msg_type()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
/// [RefMaker::from_prefix], and
/// [RefMakerRawMsgTypeExt::parse_request_msg_type].
fn request_msg_type_from_prefix(self) -> anyhow::Result<RequestMsgType> {
self.msg_type_maker()
.from_prefix()?
.parse_request_msg_type()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
/// [RefMaker::from_suffix], and
/// [RefMakerRawMsgTypeExt::parse_request_msg_type].
fn request_msg_type_from_suffix(self) -> anyhow::Result<RequestMsgType> {
self.msg_type_maker()
.from_suffix()?
.parse_request_msg_type()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
fn response_msg_type(self) -> anyhow::Result<ResponseMsgType> {
self.msg_type_maker().parse_response_msg_type()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
/// [RefMaker::from_prefix], and
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
fn response_msg_type_from_prefix(self) -> anyhow::Result<ResponseMsgType> {
self.msg_type_maker()
.from_prefix()?
.parse_response_msg_type()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker],
/// [RefMaker::from_suffix], and
/// [RefMakerRawMsgTypeExt::parse_response_msg_type].
fn response_msg_type_from_suffix(self) -> anyhow::Result<ResponseMsgType> {
self.msg_type_maker()
.from_suffix()?
.parse_response_msg_type()
}
/// Shorthand for the use of [RequestRef::parse] in chaining.
fn parse_request(self) -> anyhow::Result<RequestRef<Self>> {
RequestRef::parse(self)
}
/// Shorthand for the use of [RequestRef::parse_from_prefix] in chaining.
fn parse_request_from_prefix(self) -> anyhow::Result<RequestRef<Self>> {
RequestRef::parse_from_prefix(self)
}
/// Shorthand for the use of [RequestRef::parse_from_suffix] in chaining.
fn parse_request_from_suffix(self) -> anyhow::Result<RequestRef<Self>> {
RequestRef::parse_from_suffix(self)
}
/// Shorthand for the use of [ResponseRef::parse] in chaining.
fn parse_response(self) -> anyhow::Result<ResponseRef<Self>> {
ResponseRef::parse(self)
}
/// Shorthand for the use of [ResponseRef::parse_from_prefix] in chaining.
fn parse_response_from_prefix(self) -> anyhow::Result<ResponseRef<Self>> {
ResponseRef::parse_from_prefix(self)
}
/// Shorthand for the use of [ResponseRef::parse_from_suffix] in chaining.
fn parse_response_from_suffix(self) -> anyhow::Result<ResponseRef<Self>> {
ResponseRef::parse_from_suffix(self)
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn ping_request_maker(self) -> RefMaker<Self, PingRequest> {
self.zk_ref_maker()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn ping_request(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn ping_request_from_prefix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn ping_request_from_suffix(self) -> anyhow::Result<Ref<Self, PingRequest>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn ping_response_maker(self) -> RefMaker<Self, PingResponse> {
self.zk_ref_maker()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn ping_response(self) -> anyhow::Result<Ref<Self, PingResponse>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn ping_response_from_prefix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn ping_response_from_suffix(self) -> anyhow::Result<Ref<Self, PingResponse>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn supply_keypair_request(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn supply_keypair_request_from_prefix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn supply_keypair_request_from_suffix(self) -> anyhow::Result<Ref<Self, SupplyKeypairRequest>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn supply_keypair_response_maker(self) -> RefMaker<Self, SupplyKeypairResponse> {
self.zk_ref_maker()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn supply_keypair_response(self) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn supply_keypair_response_from_prefix(
self,
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn supply_keypair_response_from_suffix(
self,
) -> anyhow::Result<Ref<Self, SupplyKeypairResponse>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn add_listen_socket_request(self) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn add_listen_socket_request_from_prefix(
self,
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn add_listen_socket_request_from_suffix(
self,
) -> anyhow::Result<Ref<Self, super::AddListenSocketRequest>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn add_listen_socket_response_maker(self) -> RefMaker<Self, super::AddListenSocketResponse> {
self.zk_ref_maker()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn add_listen_socket_response(
self,
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn add_listen_socket_response_from_prefix(
self,
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn add_listen_socket_response_from_suffix(
self,
) -> anyhow::Result<Ref<Self, super::AddListenSocketResponse>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn add_psk_broker_request(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn add_psk_broker_request_from_prefix(
self,
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn add_psk_broker_request_from_suffix(
self,
) -> anyhow::Result<Ref<Self, super::AddPskBrokerRequest>> {
self.zk_parse_suffix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_ref_maker].
fn add_psk_broker_response_maker(self) -> RefMaker<Self, super::AddPskBrokerResponse> {
self.zk_ref_maker()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse].
fn add_psk_broker_response(self) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
self.zk_parse()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_prefix].
fn add_psk_broker_response_from_prefix(
self,
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {
self.zk_parse_prefix()
}
/// Shorthand for the typed use of [ZerocopySliceExt::zk_parse_suffix].
fn add_psk_broker_response_from_suffix(
self,
) -> anyhow::Result<Ref<Self, super::AddPskBrokerResponse>> {

View File

@@ -4,41 +4,16 @@ use rosenpass_util::zerocopy::RefMaker;
use super::RawMsgType;
/// Trait implemented by all the Rosenpass API message types.
///
/// Implemented by the message as including the message envelope; e.g.
/// [crate::api::PingRequest] but not by [crate::api::PingRequestPayload].
pub trait Message {
/// The payload this API message contains. E.g. this is [crate::api::PingRequestPayload] for [[crate::api::PingRequest].
type Payload;
/// Either [crate::api::RequestMsgType] or [crate::api::ResponseMsgType]
type MessageClass: Into<RawMsgType>;
/// The specific message type in the [Self::MessageClass].
/// E.g. this is [crate::api::RequestMsgType::Ping] for [crate::api::PingRequest]
const MESSAGE_TYPE: Self::MessageClass;
/// Wraps the payload into the envelope
///
/// # Examples
///
/// See [crate::api::PingRequest::from_payload]
fn from_payload(payload: Self::Payload) -> Self;
/// Initialize the message;
/// just sets the message type [crate::api::Envelope::msg_type].
///
/// # Examples
///
/// See [crate::api::PingRequest::init]
fn init(&mut self);
/// Initialize the message from a raw buffer: Zeroize the buffer and then call [Self::init].
///
/// # Examples
///
/// See [crate::api::PingRequest::setup]
fn setup<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self>>;
}
/// Additional convenience functions for working with [rosenpass_util::zerocopy::RefMaker]
pub trait ZerocopyResponseMakerSetupMessageExt<B, T> {
fn setup_msg(self) -> anyhow::Result<Ref<B, T>>;
}
@@ -48,27 +23,6 @@ where
B: ByteSliceMut,
T: Message,
{
/// Initialize the message using [Message::setup].
///
/// # Examples
///
/// ```
/// use rosenpass::api::{
/// PingRequest, ZerocopyResponseMakerSetupMessageExt, PING_REQUEST,
/// };
/// use rosenpass_util::zerocopy::RefMaker;
/// use std::mem::size_of;
///
/// let mut buf = [0u8; { size_of::<PingRequest>() }];
///
/// let rm = RefMaker::<&mut [u8], PingRequest>::new(&mut buf);
/// let msg: zerocopy::Ref<_, PingRequest> = rm.setup_msg()?;
///
/// let t = msg.msg_type; // Deal with unaligned read
/// assert_eq!(t, PING_REQUEST);
///
/// Ok::<(), anyhow::Error>(())
/// ```
fn setup_msg(self) -> anyhow::Result<Ref<B, T>> {
T::setup(self.into_buf())
}

View File

@@ -35,15 +35,10 @@ const ADD_PSK_BROKER_REQUEST: RawMsgType =
const ADD_PSK_BROKER_RESPONSE: RawMsgType =
RawMsgType::from_le_bytes(hex!("bd25 e418 ffb0 6930 248b 217e 2fae e353"));
/// Message properties global to the message type
pub trait MessageAttributes {
/// Get the size of the message
///
/// # Exampleds
fn message_size(&self) -> usize;
}
/// API request message types as an enum
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum RequestMsgType {
Ping,
@@ -52,7 +47,6 @@ pub enum RequestMsgType {
AddPskBroker,
}
/// API response messages types as an enum
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum ResponseMsgType {
Ping,
@@ -137,17 +131,8 @@ impl From<ResponseMsgType> for RawMsgType {
}
}
/// Extension trait for [RawMsgType].
///
/// We are using an extension trait rather than just using methods
/// because [RawMsgType] is a type alias, so we can not define methods
/// on it.
pub trait RawMsgTypeExt {
/// Try to convert this to a [RequestMsgType]; alias for the appropriate [TryFrom]
/// implementation
fn into_request_msg_type(self) -> Result<RequestMsgType, RosenpassError>;
/// Try to convert this to a [ResponseMsgType]; alias for the appropriate [TryFrom]
/// implementation
fn into_response_msg_type(self) -> Result<ResponseMsgType, RosenpassError>;
}
@@ -161,11 +146,8 @@ impl RawMsgTypeExt for RawMsgType {
}
}
/// Extension trait for [rosenpass_util::zerocopy::RefMaker].
pub trait RefMakerRawMsgTypeExt {
/// Parse a request message type from bytes
fn parse_request_msg_type(self) -> anyhow::Result<RequestMsgType>;
/// Parse a response message type from bytes
fn parse_response_msg_type(self) -> anyhow::Result<ResponseMsgType>;
}

View File

@@ -1,7 +1,3 @@
//! Boring, repetitive code related to message parsing for the API.
//!
//! Most of this should be automatically generated though some derive macro at some point.
mod byte_slice_ext;
mod message_trait;
mod message_type;

View File

@@ -3,14 +3,11 @@ use zerocopy::{AsBytes, ByteSliceMut, FromBytes, FromZeroes, Ref};
use super::{Message, RawMsgType, RequestMsgType, ResponseMsgType};
/// Size required to fit any request message in binary form
/// Size required to fit any message in binary form
pub const MAX_REQUEST_LEN: usize = 2500; // TODO fix this
/// Size required to fit any response message in binary form
pub const MAX_RESPONSE_LEN: usize = 2500; // TODO fix this
/// Maximum number of file descriptors that can be sent in a request.
pub const MAX_REQUEST_FDS: usize = 2;
/// Message envelope for API messages
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct Envelope<M: AsBytes + FromBytes> {
@@ -20,12 +17,9 @@ pub struct Envelope<M: AsBytes + FromBytes> {
pub payload: M,
}
/// Message envelope for API requests
pub type RequestEnvelope<M> = Envelope<M>;
/// Message envelope for API responses
pub type ResponseEnvelope<M> = Envelope<M>;
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct PingRequestPayload {
@@ -33,11 +27,9 @@ pub struct PingRequestPayload {
pub echo: [u8; 256],
}
#[allow(missing_docs)]
pub type PingRequest = RequestEnvelope<PingRequestPayload>;
impl PingRequest {
#[allow(missing_docs)]
pub fn new(echo: [u8; 256]) -> Self {
Self::from_payload(PingRequestPayload { echo })
}
@@ -66,7 +58,6 @@ impl Message for PingRequest {
}
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct PingResponsePayload {
@@ -74,11 +65,9 @@ pub struct PingResponsePayload {
pub echo: [u8; 256],
}
#[allow(missing_docs)]
pub type PingResponse = ResponseEnvelope<PingResponsePayload>;
impl PingResponse {
#[allow(missing_docs)]
pub fn new(echo: [u8; 256]) -> Self {
Self::from_payload(PingResponsePayload { echo })
}
@@ -107,12 +96,10 @@ impl Message for PingResponse {
}
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct SupplyKeypairRequestPayload {}
#[allow(missing_docs)]
pub type SupplyKeypairRequest = RequestEnvelope<SupplyKeypairRequestPayload>;
impl Default for SupplyKeypairRequest {
@@ -122,7 +109,6 @@ impl Default for SupplyKeypairRequest {
}
impl SupplyKeypairRequest {
#[allow(missing_docs)]
pub fn new() -> Self {
Self::from_payload(SupplyKeypairRequestPayload {})
}
@@ -151,35 +137,25 @@ impl Message for SupplyKeypairRequest {
}
}
#[allow(missing_docs)]
pub mod supply_keypair_response_status {
#[allow(missing_docs)]
pub const OK: u128 = 0;
#[allow(missing_docs)]
pub const KEYPAIR_ALREADY_SUPPLIED: u128 = 1;
/// TODO: This is not actually part of the API. Remove.
#[allow(missing_docs)]
// TODO: This is not actually part of the API. Remove.
pub const INTERNAL_ERROR: u128 = 2;
#[allow(missing_docs)]
pub const INVALID_REQUEST: u128 = 3;
/// TODO: Deprectaed, remove
#[allow(missing_docs)]
pub const IO_ERROR: u128 = 4;
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct SupplyKeypairResponsePayload {
#[allow(missing_docs)]
pub status: u128,
}
#[allow(missing_docs)]
pub type SupplyKeypairResponse = ResponseEnvelope<SupplyKeypairResponsePayload>;
impl SupplyKeypairResponse {
#[allow(missing_docs)]
pub fn new(status: u128) -> Self {
Self::from_payload(SupplyKeypairResponsePayload { status })
}
@@ -208,12 +184,10 @@ impl Message for SupplyKeypairResponse {
}
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct AddListenSocketRequestPayload {}
#[allow(missing_docs)]
pub type AddListenSocketRequest = RequestEnvelope<AddListenSocketRequestPayload>;
impl Default for AddListenSocketRequest {
@@ -223,7 +197,6 @@ impl Default for AddListenSocketRequest {
}
impl AddListenSocketRequest {
#[allow(missing_docs)]
pub fn new() -> Self {
Self::from_payload(AddListenSocketRequestPayload {})
}
@@ -252,28 +225,21 @@ impl Message for AddListenSocketRequest {
}
}
#[allow(missing_docs)]
pub mod add_listen_socket_response_status {
#[allow(missing_docs)]
pub const OK: u128 = 0;
#[allow(missing_docs)]
pub const INVALID_REQUEST: u128 = 1;
#[allow(missing_docs)]
pub const INTERNAL_ERROR: u128 = 2;
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct AddListenSocketResponsePayload {
pub status: u128,
}
#[allow(missing_docs)]
pub type AddListenSocketResponse = ResponseEnvelope<AddListenSocketResponsePayload>;
impl AddListenSocketResponse {
#[allow(missing_docs)]
pub fn new(status: u128) -> Self {
Self::from_payload(AddListenSocketResponsePayload { status })
}
@@ -302,23 +268,19 @@ impl Message for AddListenSocketResponse {
}
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct AddPskBrokerRequestPayload {}
#[allow(missing_docs)]
pub type AddPskBrokerRequest = RequestEnvelope<AddPskBrokerRequestPayload>;
impl Default for AddPskBrokerRequest {
#[allow(missing_docs)]
fn default() -> Self {
Self::new()
}
}
impl AddPskBrokerRequest {
#[allow(missing_docs)]
pub fn new() -> Self {
Self::from_payload(AddPskBrokerRequestPayload {})
}
@@ -347,28 +309,21 @@ impl Message for AddPskBrokerRequest {
}
}
#[allow(missing_docs)]
pub mod add_psk_broker_response_status {
#[allow(missing_docs)]
pub const OK: u128 = 0;
#[allow(missing_docs)]
pub const INVALID_REQUEST: u128 = 1;
#[allow(missing_docs)]
pub const INTERNAL_ERROR: u128 = 2;
}
#[allow(missing_docs)]
#[repr(packed)]
#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)]
pub struct AddPskBrokerResponsePayload {
pub status: u128,
}
#[allow(missing_docs)]
pub type AddPskBrokerResponse = ResponseEnvelope<AddPskBrokerResponsePayload>;
impl AddPskBrokerResponse {
#[allow(missing_docs)]
pub fn new(status: u128) -> Self {
Self::from_payload(AddPskBrokerResponsePayload { status })
}

View File

@@ -4,63 +4,24 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use super::{ByteSliceRefExt, MessageAttributes, PingRequest, RequestMsgType};
/// Helper for producing API message request references, [RequestRef].
///
/// This is to [RequestRef] as [rosenpass_util::zerocopy::RefMaker] is to
/// [zerocopy::Ref].
struct RequestRefMaker<B> {
buf: B,
msg_type: RequestMsgType,
}
impl<B: ByteSlice> RequestRef<B> {
/// Produce a [RequestRef] from a raw message buffer,
/// reading the type from the buffer
///
/// # Examples
///
/// ```
/// use zerocopy::AsBytes;
///
/// use rosenpass::api::{PingRequest, RequestRef, RequestMsgType};
///
/// let msg = PingRequest::new([0u8; 256]);
///
/// // TODO: HEISENBUG: This is necessary for some reason to make the rest of the example work
/// let typ = msg.msg_type;
/// assert_eq!(typ, rosenpass::api::PING_REQUEST);
///
/// let buf = msg.as_bytes();
/// let msg_ref = RequestRef::parse(buf)?;
/// assert!(matches!(msg_ref, RequestRef::Ping(_)));
///
/// assert_eq!(msg_ref.message_type(), RequestMsgType::Ping);
///
/// assert!(std::ptr::eq(buf, msg_ref.bytes()));
///
/// Ok::<(), anyhow::Error>(())
/// ```
pub fn parse(buf: B) -> anyhow::Result<Self> {
RequestRefMaker::new(buf)?.parse()
}
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
/// reading the type from the buffer.
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
RequestRefMaker::new(buf)?.from_prefix()?.parse()
}
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
/// reading the type from the buffer.
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
RequestRefMaker::new(buf)?.from_suffix()?.parse()
}
/// Get the message type [Self] contains
///
/// # Examples
///
/// See [Self::parse]
pub fn message_type(&self) -> RequestMsgType {
match self {
Self::Ping(_) => RequestMsgType::Ping,
@@ -149,7 +110,6 @@ impl<B: ByteSlice> RequestRefMaker<B> {
}
}
/// Reference to a API message response, typed as an enum.
pub enum RequestRef<B> {
Ping(Ref<B, PingRequest>),
SupplyKeypair(Ref<B, super::SupplyKeypairRequest>),
@@ -161,11 +121,6 @@ impl<B> RequestRef<B>
where
B: ByteSlice,
{
/// Access the byte data of this reference
///
/// # Examples
///
/// See [Self::parse].
pub fn bytes(&self) -> &[u8] {
match self {
Self::Ping(r) => r.bytes(),
@@ -180,7 +135,6 @@ impl<B> RequestRef<B>
where
B: ByteSliceMut,
{
/// Access the byte data of this reference; mutably
pub fn bytes_mut(&mut self) -> &[u8] {
match self {
Self::Ping(r) => r.bytes_mut(),

View File

@@ -6,29 +6,23 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use super::{Message, PingRequest, PingResponse};
use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt};
/// Extension trait for [Message]s that are requests messages
pub trait RequestMsg: Sized + Message {
/// The response message belonging to this request message
type ResponseMsg: ResponseMsg;
/// Construct a response make for this particular message
fn zk_response_maker<B: ByteSlice>(buf: B) -> RefMaker<B, Self::ResponseMsg> {
buf.zk_ref_maker()
}
/// Setup a response maker (through [Message::setup]) for this request message type
fn setup_response<B: ByteSliceMut>(buf: B) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
Self::zk_response_maker(buf).setup_msg()
}
/// Setup a response maker from a buffer prefix (through [Message::setup]) for this request message type
fn setup_response_from_prefix<B: ByteSliceMut>(
buf: B,
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
Self::zk_response_maker(buf).from_prefix()?.setup_msg()
}
/// Setup a response maker from a buffer suffix (through [Message::setup]) for this request message type
fn setup_response_from_suffix<B: ByteSliceMut>(
buf: B,
) -> anyhow::Result<Ref<B, Self::ResponseMsg>> {
@@ -36,7 +30,6 @@ pub trait RequestMsg: Sized + Message {
}
}
/// Extension trait for [Message]s that are response messages
pub trait ResponseMsg: Message {
type RequestMsg: RequestMsg;
}
@@ -73,25 +66,20 @@ impl ResponseMsg for super::AddPskBrokerResponse {
type RequestMsg = super::AddPskBrokerRequest;
}
/// Request and response for the [crate::api::RequestMsgType::Ping] message type
pub type PingPair<B1, B2> = (Ref<B1, PingRequest>, Ref<B2, PingResponse>);
/// Request and response for the [crate::api::RequestMsgType::SupplyKeypair] message type
pub type SupplyKeypairPair<B1, B2> = (
Ref<B1, super::SupplyKeypairRequest>,
Ref<B2, super::SupplyKeypairResponse>,
);
/// Request and response for the [crate::api::RequestMsgType::AddListenSocket] message type
pub type AddListenSocketPair<B1, B2> = (
Ref<B1, super::AddListenSocketRequest>,
Ref<B2, super::AddListenSocketResponse>,
);
/// Request and response for the [crate::api::RequestMsgType::AddPskBroker] message type
pub type AddPskBrokerPair<B1, B2> = (
Ref<B1, super::AddPskBrokerRequest>,
Ref<B2, super::AddPskBrokerResponse>,
);
/// A pair of references to messages; request and response each.
pub enum RequestResponsePair<B1, B2> {
Ping(PingPair<B1, B2>),
SupplyKeypair(SupplyKeypairPair<B1, B2>),
@@ -128,7 +116,6 @@ where
B1: ByteSlice,
B2: ByteSlice,
{
/// Returns a tuple to both the request and the response message
pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) {
match self {
Self::Ping((req, res)) => {
@@ -154,12 +141,10 @@ where
}
}
/// Returns the request message
pub fn request(&self) -> RequestRef<&[u8]> {
self.both().0
}
/// Returns the response message
pub fn response(&self) -> ResponseRef<&[u8]> {
self.both().1
}
@@ -170,7 +155,6 @@ where
B1: ByteSliceMut,
B2: ByteSliceMut,
{
/// Returns a mutable tuple to both the request and the response message
pub fn both_mut(&mut self) -> (RequestRef<&mut [u8]>, ResponseRef<&mut [u8]>) {
match self {
Self::Ping((req, res)) => {
@@ -196,12 +180,10 @@ where
}
}
/// Returns the request message, mutably
pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> {
self.both_mut().0
}
/// Returns the response message, mutably
pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> {
self.both_mut().1
}

View File

@@ -5,66 +5,24 @@ use zerocopy::{ByteSlice, ByteSliceMut, Ref};
use super::{ByteSliceRefExt, MessageAttributes, PingResponse, ResponseMsgType};
/// Helper for producing API message response references, [ResponseRef].
///
/// This is to [ResponseRef] as [rosenpass_util::zerocopy::RefMaker] is to
/// [zerocopy::Ref].
struct ResponseRefMaker<B> {
/// Buffer we are referencing
buf: B,
/// Message type we are producing
msg_type: ResponseMsgType,
}
impl<B: ByteSlice> ResponseRef<B> {
/// Produce a [ResponseRef] from a raw message buffer,
/// reading the type from the buffer
///
/// # Examples
///
/// ```
/// use zerocopy::AsBytes;
///
/// use rosenpass::api::{PingResponse, ResponseRef, ResponseMsgType};
/// // Produce the original PingResponse
/// let msg = PingResponse::new([0u8; 256]);
///
/// // TODO: HEISENBUG: This is necessary for some reason to make the rest of the example work
/// let typ = msg.msg_type;
/// assert_eq!(typ, rosenpass::api::PING_RESPONSE);
///
/// // Parse as a message type
/// let buf = msg.as_bytes();
/// let msg_ref = ResponseRef::parse(buf)?;
/// assert!(matches!(msg_ref, ResponseRef::Ping(_)));
///
/// // Buffers and message types of course match what we expect
/// assert_eq!(msg_ref.message_type(), ResponseMsgType::Ping);
/// assert!(std::ptr::eq(buf, msg_ref.bytes()));
///
/// Ok::<(), anyhow::Error>(())
/// ```
pub fn parse(buf: B) -> anyhow::Result<Self> {
ResponseRefMaker::new(buf)?.parse()
}
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
/// reading the type from the buffer.
pub fn parse_from_prefix(buf: B) -> anyhow::Result<Self> {
ResponseRefMaker::new(buf)?.from_prefix()?.parse()
}
/// Produce a [ResponseRef] from the prefix of a raw message buffer,
/// reading the type from the buffer.
pub fn parse_from_suffix(buf: B) -> anyhow::Result<Self> {
ResponseRefMaker::new(buf)?.from_suffix()?.parse()
}
/// Get the message type [Self] contains
///
/// # Examples
///
/// See [Self::parse]
pub fn message_type(&self) -> ResponseMsgType {
match self {
Self::Ping(_) => ResponseMsgType::Ping,
@@ -153,7 +111,6 @@ impl<B: ByteSlice> ResponseRefMaker<B> {
}
}
/// Reference to a API message response, typed.
pub enum ResponseRef<B> {
Ping(Ref<B, PingResponse>),
SupplyKeypair(Ref<B, super::SupplyKeypairResponse>),
@@ -165,11 +122,6 @@ impl<B> ResponseRef<B>
where
B: ByteSlice,
{
/// Access the byte data of this reference
///
/// # Examples
///
/// See [Self::parse].
pub fn bytes(&self) -> &[u8] {
match self {
Self::Ping(r) => r.bytes(),
@@ -184,7 +136,6 @@ impl<B> ResponseRef<B>
where
B: ByteSliceMut,
{
/// Access the byte data of this reference; mutably
pub fn bytes_mut(&mut self) -> &[u8] {
match self {
Self::Ping(r) => r.bytes_mut(),

View File

@@ -2,21 +2,10 @@ use super::{ByteSliceRefExt, Message, PingRequest, PingResponse, RequestRef, Req
use std::{collections::VecDeque, os::fd::OwnedFd};
use zerocopy::{ByteSlice, ByteSliceMut};
/// The rosenpass API implementation functions.
///
/// Implemented by [crate::api::ApiHandler].
///
/// # Examples
///
/// See the example of how to use the API in [crate::api].
pub trait Server {
/// This implements the handler for the [crate::api::RequestMsgType::Ping] API message
///
/// It merely takes a buffer and returns that same buffer.
///
/// # Examples
///
/// See the example of how to use the API in [crate::api].
fn ping(
&mut self,
req: &PingRequest,
@@ -65,10 +54,6 @@ pub trait Server {
/// The file descriptors for the keys need not be backed by a file on disk. You can supply a
/// [memfd](https://man.archlinux.org/man/memfd.2.en) or [memfd_secret](https://man.archlinux.org/man/memfd_secret.2.en)
/// backed file descriptor if the server keys are not backed by a file system file.
///
/// # Examples
///
/// See the example of how to use the API in [crate::api].
fn supply_keypair(
&mut self,
req: &super::SupplyKeypairRequest,
@@ -95,13 +80,8 @@ pub trait Server {
///
/// # Description
///
/// This endpoint allows you to supply a UDP listen socket; it will be used to perform key
/// key exchanges using the Rosenpass protocol.
/// This endpoint allows you to supply a UDP listen socket; it will be used to perform
/// cryptographic key exchanges via the Rosenpass protocol.
///
/// # Examples
///
/// See the example of how to use the API in [crate::api].
fn add_listen_socket(
&mut self,
req: &super::AddListenSocketRequest,
@@ -109,31 +89,6 @@ pub trait Server {
res: &mut super::AddListenSocketResponse,
) -> anyhow::Result<()>;
/// Supply a new PSK broker listen socket through file descriptor passing via the API
///
/// This implements the handler for the [crate::api::RequestMsgType::AddPskBroker] API message.
///
/// # File descriptors
///
/// 1. The listen socket; must be backed by a unix domain stream socket
///
/// # API Return Status
///
/// 1. [crate::api::add_psk_broker_response_status::OK] - Indicates success
/// 2. [crate::api::add_psk_broker_response_status::INVALID_REQUEST] Malformed request; could be:
/// - Missing file descriptors for public key
/// - Invalid file descriptor type
/// 3. [crate::api::add_psk_broker_response_status::INTERNAL_ERROR] Some other, non-fatal error
/// occured. Check the logs on log
///
/// # Description
///
/// This endpoint allows you to supply a UDP listen socket; it will be used to transmit
/// cryptographic keys exchanged to WireGuard.
///
/// # Examples
///
/// See the example of how to use the API in [crate::api].
fn add_psk_broker(
&mut self,
req: &super::AddPskBrokerRequest,
@@ -141,11 +96,6 @@ pub trait Server {
res: &mut super::AddPskBrokerResponse,
) -> anyhow::Result<()>;
/// Similar to [Self::handle_message], but takes a [RequestResponsePair]
/// instead of taking to separate byte buffers.
///
/// I.e. this function uses the explicit type tag encoded in [RequestResponsePair]
/// rather than reading the type tag from the request buffer.
fn dispatch<ReqBuf, ResBuf>(
&mut self,
p: &mut RequestResponsePair<ReqBuf, ResBuf>,
@@ -167,14 +117,6 @@ pub trait Server {
}
}
/// Called by [crate::api::mio::MioConnection] when a new API request was received.
///
/// The parameters are:
///
/// - `req` A buffer containing the request
/// - `res_fds` A list of file descriptors received during the API call (i.e. this is used
/// with unix socket file descriptor passing)
/// - `res` The buffer to store the response in.
fn handle_message<ReqBuf, ResBuf>(
&mut self,
req: ReqBuf,

View File

@@ -6,7 +6,6 @@ use crate::config::Rosenpass as RosenpassConfig;
use super::config::ApiConfig;
/// Additional command line arguments for the API
#[cfg(feature = "experiment_api")]
#[derive(Args, Debug)]
pub struct ApiCli {
@@ -28,14 +27,10 @@ pub struct ApiCli {
}
impl ApiCli {
/// Copy the parameters set here into the [RosenpassConfig].
/// Forwards to [Self::apply_to_api_config]:
pub fn apply_to_config(&self, cfg: &mut RosenpassConfig) -> anyhow::Result<()> {
self.apply_to_api_config(&mut cfg.api)
}
/// Fills the values from [ApiConfig::listen_path], [ApiConfig::listen_fd], and
/// [ApiConfig::stream_fd] with the values from [Self]
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);

View File

@@ -6,8 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::app_server::AppServer;
/// Configuration options for the Rosenpass API
#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)]
#[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
@@ -24,10 +23,6 @@ pub struct ApiConfig {
}
impl ApiConfig {
/// Construct appropriate [UnixListener]s for each of the API
/// listeners and connections configured in [Self] and invoke
/// [AppServer::add_api_listener] for each to add them to the
/// [AppServer].
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)?)?;
@@ -44,12 +39,10 @@ impl ApiConfig {
Ok(())
}
/// Sum of all the API sources configured in here
pub fn count_api_sources(&self) -> usize {
self.listen_path.len() + self.listen_fd.len() + self.stream_fd.len()
}
/// Checks if [Self::count_api_sources] is greater than zero
pub fn has_api_sources(&self) -> bool {
self.count_api_sources() > 0
}

View File

@@ -53,9 +53,6 @@ struct MioConnectionBuffers {
}
#[derive(Debug)]
/// Represents a single connection with an API client.
/// Includes the necessary buffers, the [ApiHandler],
/// and the [UnixStream] that is used for communication.
pub struct MioConnection {
io: UnixStream,
mio_token: mio::Token,
@@ -65,8 +62,6 @@ pub struct MioConnection {
}
impl MioConnection {
/// Construct a new [Self] for the given app server from the unix socket stream
/// to communicate on.
pub fn new(app_server: &mut AppServer, mut io: UnixStream) -> std::io::Result<Self> {
let mio_token = app_server.mio_token_dispenser.dispense();
app_server
@@ -93,8 +88,6 @@ impl MioConnection {
})
}
/// Checks if this unix stream should be closed by the enclosing
/// structure
pub fn should_close(&self) -> bool {
let exhausted = self
.buffers
@@ -104,30 +97,22 @@ impl MioConnection {
self.invalid_read && exhausted
}
/// Close and deregister this particular API connection
pub fn close(mut self, app_server: &mut AppServer) -> anyhow::Result<()> {
app_server.mio_poll.registry().deregister(&mut self.io)?;
Ok(())
}
/// Retrieve the mio token
pub fn mio_token(&self) -> mio::Token {
self.mio_token
}
}
/// We require references to both [MioConnection] and to the [AppServer] that contains it.
pub trait MioConnectionContext {
/// Reference to the [MioConnection] we are focusing on
fn mio_connection(&self) -> &MioConnection;
/// Reference to the [AppServer] that contains the [Self::mio_connection]
fn app_server(&self) -> &AppServer;
/// Mutable reference to the [MioConnection] we are focusing on
fn mio_connection_mut(&mut self) -> &mut MioConnection;
/// Mutable reference to the [AppServer] that contains the [Self::mio_connection]
fn app_server_mut(&mut self) -> &mut AppServer;
/// Called by [AppServer::poll] regularly to process any incoming (and outgoing) API messages
fn poll(&mut self) -> anyhow::Result<()> {
macro_rules! short {
($e:expr) => {
@@ -148,7 +133,6 @@ pub trait MioConnectionContext {
Ok(())
}
/// Called by [Self::poll] to process incoming messages
fn handle_incoming_message(&mut self) -> anyhow::Result<Option<()>> {
self.with_buffers_stolen(|this, bufs| {
// Acquire request & response. Caller is responsible to make sure
@@ -172,7 +156,6 @@ pub trait MioConnectionContext {
})
}
/// Called by [Self::poll] to write data in the send buffer to the unix stream
fn flush_write_buffer(&mut self) -> anyhow::Result<Option<()>> {
if self.write_buf_mut().exhausted() {
return Ok(Some(()));
@@ -211,7 +194,6 @@ pub trait MioConnectionContext {
}
}
/// Called by [Self::poll] to check for messages to receive
fn recv(&mut self) -> anyhow::Result<Option<()>> {
if !self.write_buf_mut().exhausted() || self.mio_connection().invalid_read {
return Ok(None);
@@ -275,12 +257,10 @@ pub trait MioConnectionContext {
}
}
/// Forwards to [MioConnection::mio_token]
fn mio_token(&self) -> mio::Token {
self.mio_connection().mio_token()
}
/// Forwards to [MioConnection::should_close]
fn should_close(&self) -> bool {
self.mio_connection().should_close()
}
@@ -319,7 +299,6 @@ trait MioConnectionContextPrivate: MioConnectionContext {
impl<T> MioConnectionContextPrivate for T where T: ?Sized + MioConnectionContext {}
/// Every [MioConnectionContext] is also a [ApiHandlerContext]
impl<T> ApiHandlerContext for T
where
T: ?Sized + MioConnectionContext,

View File

@@ -10,59 +10,41 @@ use crate::app_server::{AppServer, AppServerIoSource};
use super::{MioConnection, MioConnectionContext};
/// This is in essence a unix listener for API connections.
///
/// It contains a number of [UnixListener]s and the associated [MioConnection]s encapsulating [mio::net::UnixListener]s.
#[derive(Default, Debug)]
pub struct MioManager {
listeners: Vec<UnixListener>,
connections: Vec<Option<MioConnection>>,
}
/// Points at a particular source of IO events inside [MioManager]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum MioManagerIoSource {
// Source of IO events is the Nth unix socket listener (see [MioManager::listeners])
Listener(usize),
// Source of IO events is the Nth unix socket listener (see [MioManager::connections])
Connection(usize),
}
impl MioManager {
/// Construct an empty [Self]
pub fn new() -> Self {
Self::default()
}
}
/// Focus in on a particular [MioConnection] inside a [MioManager]
///
/// This is mainly used to implement [MioConnectionContext].
struct MioConnectionFocus<'a, T: ?Sized + MioManagerContext> {
/// [MioConnectionContext] to access the [MioManager] instance and [AppServer]
ctx: &'a mut T,
/// Index of the connection referenced to by [Self]
conn_idx: usize,
}
impl<'a, T: ?Sized + MioManagerContext> MioConnectionFocus<'a, T> {
/// Produce a MioConnectionContext from the [MioConnectionContext] and the connection index
fn new(ctx: &'a mut T, conn_idx: usize) -> Self {
Self { ctx, conn_idx }
}
}
pub trait MioManagerContext {
/// Reference to the [MioManager]
fn mio_manager(&self) -> &MioManager;
/// Reference to the [MioManager], mutably
fn mio_manager_mut(&mut self) -> &mut MioManager;
/// Reference to the [AppServer] this [MioManager] is associated with
fn app_server(&self) -> &AppServer;
/// Mutable reference to the [AppServer] this [MioManager] is associated with
fn app_server_mut(&mut self) -> &mut AppServer;
/// Add a new [UnixListener] to listen for API connections on
fn add_listener(&mut self, mut listener: UnixListener) -> io::Result<()> {
let srv = self.app_server_mut();
let mio_token = srv.mio_token_dispenser.dispense();
@@ -82,7 +64,6 @@ pub trait MioManagerContext {
Ok(())
}
/// Add a new connection to an API client
fn add_connection(&mut self, connection: UnixStream) -> io::Result<()> {
let connection = MioConnection::new(self.app_server_mut(), connection)?;
let mio_token = connection.mio_token();
@@ -103,7 +84,6 @@ pub trait MioManagerContext {
Ok(())
}
/// Poll a particular [MioManagerIoSource] in this [MioManager]
fn poll_particular(&mut self, io_source: MioManagerIoSource) -> anyhow::Result<()> {
use MioManagerIoSource as S;
match io_source {
@@ -113,14 +93,12 @@ pub trait MioManagerContext {
Ok(())
}
/// Check for new connections and poll all the [MioConnectionContext]s managed by [Self]
fn poll(&mut self) -> anyhow::Result<()> {
self.accept_connections()?;
self.poll_connections()?;
Ok(())
}
/// Check all the [UnixListener]s managed by this [MioManager] for new connections
fn accept_connections(&mut self) -> io::Result<()> {
for idx in 0..self.mio_manager_mut().listeners.len() {
self.accept_from(idx)?;
@@ -128,7 +106,6 @@ pub trait MioManagerContext {
Ok(())
}
/// Check a particular [UnixListener] managed by this for new connections.
fn accept_from(&mut self, idx: usize) -> 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
@@ -145,7 +122,6 @@ pub trait MioManagerContext {
Ok(())
}
/// Call [MioConnectionContext::poll] on all the [MioConnection]s in This
fn poll_connections(&mut self) -> anyhow::Result<()> {
for idx in 0..self.mio_manager().connections.len() {
self.poll_particular_connection(idx)?;
@@ -153,7 +129,6 @@ pub trait MioManagerContext {
Ok(())
}
/// Call [MioConnectionContext::poll] on a particular connection
fn poll_particular_connection(&mut self, idx: usize) -> anyhow::Result<()> {
if self.mio_manager().connections[idx].is_none() {
return Ok(());

View File

@@ -1,10 +1,4 @@
//! The bulk code relating to the Rosenpass unix socket API
//!
//! # Examples
//!
#![doc = "```ignore"]
#![doc = include_str!("../../tests/api-integration-tests-api-setup.rs")]
#![doc = "```"]
mod api_handler;
mod boilerplate;

View File

@@ -1,5 +1,3 @@
/// This contains the bulk of the rosenpass server IO handling code whereas
/// the actual cryptographic code lives in the [crate::protocol] module
use anyhow::bail;
use anyhow::Context;
@@ -51,78 +49,33 @@ use crate::{
use rosenpass_util::attempt;
use rosenpass_util::b64::B64Display;
/// The maximum size of a base64 encoded symmetric key (estimate)
pub const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
/// The maximum size of a base64 peer ID (estimate)
pub const MAX_B64_PEER_ID_SIZE: usize = 32 * 5 / 3;
const MAX_B64_KEY_SIZE: usize = 32 * 5 / 3;
const MAX_B64_PEER_ID_SIZE: usize = 32 * 5 / 3;
/// The zero IPv4 address; this is generally used to tell network servers to choose any interface
/// when listening
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
/// The zero IPv6 address; this is generally used to tell network servers to choose any interface
/// when listening
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
/// Ratio of blocking epoll(7) polls to non-blocking polls at which the rosenpass server
/// assumes it is under load (i.e. a DOS attack may be happening)
const UNDER_LOAD_RATIO: f64 = 0.5;
/// Period at which the DOS detection code updates whether there is an "under load" status
const DURATION_UPDATE_UNDER_LOAD_STATUS: Duration = Duration::from_millis(500);
pub const BROKER_ID_BYTES: usize = 8;
const BROKER_ID_BYTES: usize = 8;
/// IPv4 address that tells the network layer to listen on any interface
///
/// # Examples
///
/// See [AppServer::new].
pub fn ipv4_any_binding() -> SocketAddr {
fn ipv4_any_binding() -> SocketAddr {
// addr, port
SocketAddr::V4(SocketAddrV4::new(IPV4_ANY_ADDR, 0))
}
/// IPv6 address that tells the network layer to listen on any interface
///
/// # Examples
///
/// See [AppServer::new].
pub fn ipv6_any_binding() -> SocketAddr {
fn ipv6_any_binding() -> SocketAddr {
// addr, port, flowinfo, scope_id
SocketAddr::V6(SocketAddrV6::new(IPV6_ANY_ADDR, 0, 0, 0))
}
/// This is used to assign indices to MIO (epoll) event sources
#[derive(Debug, Default)]
pub struct MioTokenDispenser {
pub counter: usize,
counter: usize,
}
impl MioTokenDispenser {
/// Produces a single IO event source ID
///
/// # Examples
///
/// Use is quite straightforward:
///
/// ```
/// use rosenpass::app_server::MioTokenDispenser;
/// use mio::Token;
///
/// let mut dispenser = MioTokenDispenser {
/// counter: 0
/// };
///
/// let t1 = dispenser.dispense();
/// let t2 = dispenser.dispense();
///
/// assert_ne!(t1, t2);
///
/// // If you inspected the output, you would find that the dispenser is really just a counter.
/// // Though this is an implementation detail
/// assert_eq!(t1, Token(0));
/// assert_eq!(t2, Token(1));
/// ```
///
pub fn dispense(&mut self) -> Token {
let r = self.counter;
self.counter += 1;
@@ -130,132 +83,42 @@ impl MioTokenDispenser {
}
}
/// List of WireGuard brokers
///
/// Each WireGuard peer ([AppPeer]) is assigned a broker ([AppPeer::broker_peer]).
///
/// When a new has been exchanged, then its associated broker is called to transmit the key
/// to WireGuard (or to do something else).
///
/// Brokers live in [AppServer::brokers]. They are added/removed from the AppServer via
/// [AppServer::register_broker] and [AppServer::unregister_broker]. PSKs are distributed
/// to their respective brokers via [AppPeerPtr::set_psk].
///
/// Note that the entire broker system is an experimental feature; it is not up to the
/// same quality standards as the rest of the code. In particular, the use of [BROKER_ID_BYTES]
/// and a hash map is simultaneously overengineered and not really that useful for what it is
/// doing.
#[derive(Debug, Default)]
pub struct BrokerStore {
/// The collection of WireGuard brokers. See [Self].
pub store: HashMap<
Public<BROKER_ID_BYTES>,
Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
>,
}
/// Reference to a broker imbued with utility methods
#[derive(Debug, Clone)]
pub struct BrokerStorePtr(pub Public<BROKER_ID_BYTES>);
/// This is the broker configuration for a particular broker peer
#[derive(Debug)]
pub struct BrokerPeer {
/// Reference to the broker used for this particular peer
ptr: BrokerStorePtr,
/// Configuration for a WireGuard broker.
///
/// This is woefully overengineered and there is very little reason why the broker
/// configuration should not live in the particular WireGuard broker.
peer_cfg: Box<dyn WireguardBrokerCfg>,
}
impl BrokerPeer {
/// Create a broker peer
pub fn new(ptr: BrokerStorePtr, peer_cfg: Box<dyn WireguardBrokerCfg>) -> Self {
Self { ptr, peer_cfg }
}
/// Retrieve the pointer to WireGuard PSK broker used with this peer
pub fn ptr(&self) -> &BrokerStorePtr {
&self.ptr
}
}
/// IO/BusinessLogic information for a particular protocol peer.
///
/// There is a one-to-one correspondence between this struct and [crate::protocol::Peer];
/// whereas the struct in the protocol module stores information specific to the cryptographic layer,
/// this struct stores information IO information.
#[derive(Default, Debug)]
pub struct AppPeer {
/// If set, then [AppServer::output_key] will write generated output keys
/// to a file configured here and produce information on standard out to
/// notify the calling process that
pub outfile: Option<PathBuf>,
/// If this option is set, then [AppServer::output_key] will send generated output
/// keys to the broker configured here
pub broker_peer: Option<BrokerPeer>,
/// This is the network address configured for a particular peer at program start.
///
/// I.e. this is the address the rosenpass program will send [crate::msgs::InitHello]
/// packets to, trying to exchange a key.
///
/// Note that the remote peer may connect with another address. See [Self::current_endpoint].
pub initial_endpoint: Option<Endpoint>,
/// The network address currently used for a particular peer.
///
/// This is not necessarily the address that was configured at program start (see [Self::initial_endpoint]),
/// because the remote peer can initiate handshakes from an arbitrary network address.
///
/// If another peer successfully connects to this one from any address, then this field will
/// be updated to reflect which address this was.
pub current_endpoint: Option<Endpoint>,
}
impl AppPeer {
/// Retrieve the [Endpoint] associated with this peer.
///
/// I.e. the [Self::current_endpoint] if set and [Self::initial_endpoint] otherwise.
///
/// # Examples
///
/// ```
/// use rosenpass::app_server::{Endpoint, AppPeer};
/// use rosenpass_util::functional::run;
///
/// let mut peer = AppPeer {
/// outfile: None,
/// broker_peer: None,
/// initial_endpoint: Some(Endpoint::discovery_from_hostname("0.0.0.0:0".to_string())?),
/// current_endpoint: Some(Endpoint::discovery_from_hostname("0.0.0.0:1".to_string())?),
/// };
///
/// fn same(a: Option<&Endpoint>, b: Option<&Endpoint>) -> bool {
/// if a.is_none() && b.is_none() {
/// return true;
/// }
///
/// run(|| Some(std::ptr::eq(a?, b?)) )
/// .unwrap_or(a.is_some() == b.is_some())
/// }
///
/// assert!(same(peer.endpoint(), peer.current_endpoint.as_ref()));
///
/// let mut tmp = None;
/// std::mem::swap(&mut tmp, &mut peer.initial_endpoint);
/// assert!(same(peer.endpoint(), peer.current_endpoint.as_ref()));
///
/// std::mem::swap(&mut tmp, &mut peer.initial_endpoint);
/// std::mem::swap(&mut tmp, &mut peer.current_endpoint);
/// assert!(same(peer.endpoint(), peer.initial_endpoint.as_ref()));
///
/// peer.initial_endpoint = None;
/// peer.current_endpoint = None;
/// assert!(peer.endpoint().is_none());
///
/// Ok::<(), anyhow::Error>(())
/// ```
pub fn endpoint(&self) -> Option<&Endpoint> {
self.current_endpoint
.as_ref()
@@ -263,8 +126,6 @@ impl AppPeer {
}
}
/// No longer in use since we have the broker system (see [BrokerPeer])
/// TODO: Remove
#[derive(Default, Debug)]
pub struct WireguardOut {
// impl KeyOutput
@@ -273,20 +134,12 @@ pub struct WireguardOut {
pub extra_params: Vec<String>,
}
/// Used to indicate whether the rosenpass server is in normal operating
/// conditions or under load (i.e. a DOS attack is happening)
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DoSOperation {
UnderLoad,
Normal,
}
/// Integration test helpers for AppServer
///
/// TODO: Remove; this is no way to write integration tests
///
/// # Examples
///
/// See [AppServer]
#[derive(Debug, Builder)]
#[builder(pattern = "owned")]
pub struct AppServerTest {
@@ -298,100 +151,43 @@ pub struct AppServerTest {
pub termination_handler: Option<std::sync::mpsc::Receiver<()>>,
}
/// This represents a some source of IO operations in the context of the Rosenpass server
///
/// I.e. this identifies some structure that could be marked as "ready for IO" by [mio]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum AppServerIoSource {
/// IO source refers to a socket in [AppServer::sockets]
Socket(usize),
/// IO source refers to a PSK broker in [AppServer::brokers]
PskBroker(Public<BROKER_ID_BYTES>),
/// IO source refers to some IO sources used in the API;
/// see [AppServer::api_manager]
#[cfg(feature = "experiment_api")]
MioManager(crate::api::mio::MioManagerIoSource),
}
/// Number of epoll(7) events Rosenpass can receive at a time
const EVENT_CAPACITY: usize = 20;
/// This holds pretty much all of the state of the Rosenpass application
/// including the cryptographic state in [Self::crypto_site]
/// Holds the state of the application, namely the external IO
///
/// Responsible for file IO, network IO, etc…
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/app_server_example.rs")]
#[doc = "```"]
/// Responsible for file IO, network IO
// TODO add user control via unix domain socket and stdin/stdout
#[derive(Debug)]
pub struct AppServer {
/// Contains the actual cryptographic implementation.
///
/// Because the API supports initializing the server with a keypair
/// and CryptoServer needs to be initialized with a keypair, the struct
/// struct is wrapped in a ConstructionSite
pub crypto_site: ConstructionSite<BuildCryptoServer, CryptoServer>,
/// The UDP sockets used to send and receive protocol messages
pub sockets: Vec<mio::net::UdpSocket>,
/// Buffer for [mio] (epoll(7), async IO handling) IO events
pub events: mio::Events,
/// Supplemental buffer for [mio] events. See the inline documentation of [AppServer::try_recv]
/// for details.
pub short_poll_queue: VecDeque<mio::event::Event>,
/// We have two different polling modes; long polling (legacy) and short polling (new and
/// somewhat experimental). See [AppServer::try_recv] for details
pub performed_long_poll: bool,
/// Events produced by [mio] refer to IO sources by a numeric token assigned to them
/// (see [MioTokenDispenser]). This index associateds mio token with the specific IO
/// source stored in this object
pub io_source_index: HashMap<mio::Token, AppServerIoSource>,
/// Asynchronous IO source
pub mio_poll: mio::Poll,
/// MIO associates IO sources with numeric tokens. This struct takes care of generating these
/// tokens
pub mio_token_dispenser: MioTokenDispenser,
/// Helpers handling communication with WireGuard; these take a generated key and forward it to
/// WireGuard
pub brokers: BrokerStore,
/// This is our view of the peers; generally every peer in here is associated with one peer in
/// CryptoServer
pub peers: Vec<AppPeer>,
/// If set to [Verbosity::Verbose], then some extra information will be printed
/// at the info log level
pub verbosity: Verbosity,
/// Used by [AppServer::try_recv] to ensure that all packages have been read
/// from the UDP sockets
pub all_sockets_drained: bool,
/// Whether network message handling determined that a Denial of Service attack is happening
pub under_load: DoSOperation,
/// State kept by the [AppServer::try_recv] for polling
pub blocking_polls_count: usize,
/// State kept by the [AppServer::try_recv] for polling
pub non_blocking_polls_count: usize,
/// State kept by the [AppServer::try_recv] for polling
pub unpolled_count: usize,
/// State kept by the [AppServer::try_recv] for polling
pub last_update_time: Instant,
/// Used by integration tests to force [Self] into DoS condition
/// and to terminate the AppServer after the test is complete
pub test_helpers: Option<AppServerTest>,
/// Helper for integration tests running rosenpass as a subprocess
/// to terminate properly upon receiving an appropriate system signal.
///
/// This is primarily needed for coverage testing, since llvm-cov does not
/// write coverage reports to disk when a process is stopped by the default
/// signal handler.
///
/// See <https://github.com/rosenpass/rosenpass/issues/385>
#[cfg(feature = "internal_signal_handling_for_coverage_reports")]
pub term_signal: terminate::TerminateRequested,
pub crypto_site: ConstructionSite<BuildCryptoServer, CryptoServer>,
pub sockets: Vec<mio::net::UdpSocket>,
pub events: mio::Events,
pub short_poll_queue: VecDeque<mio::event::Event>,
pub performed_long_poll: bool,
pub io_source_index: HashMap<mio::Token, AppServerIoSource>,
pub mio_poll: mio::Poll,
pub mio_token_dispenser: MioTokenDispenser,
pub brokers: BrokerStore,
pub peers: Vec<AppPeer>,
pub verbosity: Verbosity,
pub all_sockets_drained: bool,
pub under_load: DoSOperation,
pub blocking_polls_count: usize,
pub non_blocking_polls_count: usize,
pub unpolled_count: usize,
pub last_update_time: Instant,
pub test_helpers: Option<AppServerTest>,
#[cfg(feature = "experiment_api")]
/// The Rosenpass unix socket API handler; this is an experimental
/// feature that can be used to embed Rosenpass in external applications
/// via communication by unix socket
pub api_manager: crate::api::mio::MioManager,
}
@@ -405,19 +201,14 @@ pub struct AppServer {
pub struct SocketPtr(pub usize);
impl SocketPtr {
/// Retrieve the concrete udp socket associated with the pointer
pub fn get<'a>(&self, srv: &'a AppServer) -> &'a mio::net::UdpSocket {
&srv.sockets[self.0]
}
/// Retrieve the concrete udp socket associated with the pointer, mutably
pub fn get_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut mio::net::UdpSocket {
&mut srv.sockets[self.0]
}
/// Send a UDP packet to another address.
///
/// Merely forwards to [mio::net::UdpSocket::send_to]
pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> anyhow::Result<()> {
self.get(srv).send_to(buf, addr)?;
Ok(())
@@ -425,41 +216,28 @@ impl SocketPtr {
}
/// Index based pointer to a Peer
///
/// This allows retrieving both the io-oriented and the cryptographic information
/// about a peer.
#[derive(Debug, Copy, Clone)]
pub struct AppPeerPtr(pub usize);
impl AppPeerPtr {
/// Takes an pointer from the cryptography subsystem
/// in [AppServer::crypto_site] and derives the associated AppPeerPtr
/// in [AppServer]
/// Takes an index based handle and returns the actual peer
pub fn lift(p: PeerPtr) -> Self {
Self(p.0)
}
/// Turns this pointer into a cryptographic peer pointer for [CryptoServer]
/// in [AppServer::crypto_site]
/// Returns an index based handle to one Peer
pub fn lower(&self) -> PeerPtr {
PeerPtr(self.0)
}
/// Retrieve the [AppPeer] pointed to by [Self]
pub fn get_app<'a>(&self, srv: &'a AppServer) -> &'a AppPeer {
&srv.peers[self.0]
}
/// Retrieve the [AppPeer] pointed to by [Self], mutably
pub fn get_app_mut<'a>(&self, srv: &'a mut AppServer) -> &'a mut AppPeer {
&mut srv.peers[self.0]
}
/// Use the associated WireGuard PSK broker via [BrokerStorePtr]
/// to upload a new PSK.
///
/// If no PSK broker is set and [AppPeer::outfile] is none, then
/// this prints a warning
pub fn set_psk(&self, server: &mut AppServer, psk: &Secret<WG_KEY_LEN>) -> anyhow::Result<()> {
if let Some(broker) = server.peers[self.0].broker_peer.as_ref() {
let config = broker.peer_cfg.create_config(psk);
@@ -472,34 +250,17 @@ impl AppPeerPtr {
}
}
/// The result of [AppServer::poll].
///
/// Instructs [AppServer::event_loop_without_error_handling] on how to proceed.
#[derive(Debug)]
pub enum AppPollResult {
/// Erase the key for a given peer. Corresponds to [crate::protocol::PollResult::DeleteKey]
DeleteKey(AppPeerPtr),
/// Send an initiation to the given peer. Corresponds to [crate::protocol::PollResult::SendInitiation]
SendInitiation(AppPeerPtr),
/// Send a retransmission to the given peer. Corresponds to
/// [crate::protocol::PollResult::SendRetransmission]
SendRetransmission(AppPeerPtr),
/// Received a network message.
///
/// This is the only case without a correspondence in [crate::protocol::PollResult]
ReceivedMessage(usize, Endpoint),
}
/// The reason why we are outputting a key
#[derive(Debug)]
pub enum KeyOutputReason {
/// The reason is that a new key for the given peer was successfully exchanged
Exchanged,
/// The reason is, that no key could be exchanged with the peer before the output
/// key lifetime was reached; a [AppPollResult::DeleteKey] event was issued.
///
/// The key we output in this case is chosen randomly and serves to securely
/// erase whatever key is currently stored.
Stale,
}
@@ -538,21 +299,14 @@ impl std::fmt::Display for Endpoint {
}
}
/// A network address bound to a particular socket.
///
/// We need the information which socket is used because different listen sockets
/// might be on different networks.
#[derive(Debug)]
pub struct SocketBoundEndpoint {
/// The socket the address can be reached under; this is generally
/// determined when we actually receive an RespHello message
socket: SocketPtr,
/// The network address
/// Just the address
addr: SocketAddr,
/// Byte representation of this socket bound network address.
/// Generated through [SocketBoundEndpoint::to_bytes].
///
/// Read through [HostIdentification::encode]
/// identifier
bytes: (usize, [u8; SocketBoundEndpoint::BUFFER_SIZE]),
}
@@ -563,22 +317,16 @@ impl std::fmt::Display for SocketBoundEndpoint {
}
impl SocketBoundEndpoint {
/// Length in bytes of the serialized socket index
const SOCKET_SIZE: usize = usize::BITS as usize / 8;
/// Length in bytes of the serialized ipv6 address
const IPV6_SIZE: usize = 16;
/// Length in bytes of the serialized port
const PORT_SIZE: usize = 2;
/// Length in bytes of the serialized ipv6 address scope (see [SocketAddrV6::scope_id])
const SCOPE_ID_SIZE: usize = 4;
/// Length in size of
const BUFFER_SIZE: usize = SocketBoundEndpoint::SOCKET_SIZE
+ SocketBoundEndpoint::IPV6_SIZE
+ SocketBoundEndpoint::PORT_SIZE
+ SocketBoundEndpoint::SCOPE_ID_SIZE;
/// Produce a new [Self]
pub fn new(socket: SocketPtr, addr: SocketAddr) -> Self {
let bytes = Self::to_bytes(&socket, &addr);
Self {
@@ -588,7 +336,6 @@ impl SocketBoundEndpoint {
}
}
/// Computes [HostIdentification::encode] for [Self]. Value cached in [Self::bytes].
fn to_bytes(
socket: &SocketPtr,
addr: &SocketAddr,
@@ -623,18 +370,12 @@ impl HostIdentification for SocketBoundEndpoint {
}
impl Endpoint {
/// Given a list of potential network addresses, start peer discovery.
///
/// Will send initiations to different addresses given here on each [crate::msgs::InitHello]
/// retransmission during the peer discovery phase.
/// Start discovery from some addresses
pub fn discovery_from_addresses(addresses: Vec<SocketAddr>) -> Self {
Endpoint::Discovery(HostPathDiscoveryEndpoint::from_addresses(addresses))
}
/// Given a hostname, start peer discovery.
///
/// Will send initiations to different addresses assigned to the host name
/// on each [crate::msgs::InitHello] retransmission during the peer discovery phase.
/// Start endpoint discovery from a hostname
pub fn discovery_from_hostname(hostname: String) -> anyhow::Result<Self> {
let host = HostPathDiscoveryEndpoint::lookup(hostname)?;
Ok(Endpoint::Discovery(host))
@@ -665,8 +406,6 @@ impl Endpoint {
Some(Self::discovery_from_addresses(addrs))
}
/// Send a message to the address referenced by this endpoint or to one of
/// the endpoints if we are in the peer discovery phase for this endpoint
pub fn send(&self, srv: &AppServer, buf: &[u8]) -> anyhow::Result<()> {
use Endpoint::*;
match self {
@@ -675,9 +414,6 @@ impl Endpoint {
}
}
/// List of addresses this endpoint may be associated with.
///
/// During peer discovery, this can be multiple addresses.
fn addresses(&self) -> &[SocketAddr] {
use Endpoint::*;
match self {
@@ -715,14 +451,7 @@ impl Endpoint {
// TODO: We might consider adjusting the retransmission handling to account for host-path discovery
#[derive(Debug)]
pub struct HostPathDiscoveryEndpoint {
/// Round robin index the next [Self::send_scouting] call should send packets to
///
/// (address offset, socket offset)
///
/// Including the socket here accounts for the fact that some network addresses may be
/// reachable only through particular UDP sockets
scouting_state: Cell<(usize, usize)>,
/// List of addresses fir oeer discovery
scouting_state: Cell<(usize, usize)>, // addr_off, sock_off
addresses: Vec<SocketAddr>,
}
@@ -733,7 +462,6 @@ impl std::fmt::Display for HostPathDiscoveryEndpoint {
}
impl HostPathDiscoveryEndpoint {
/// Initiate a peer discovery process through a list of potential addresses
pub fn from_addresses(addresses: Vec<SocketAddr>) -> Self {
let scouting_state = Cell::new((0, 0));
Self {
@@ -742,7 +470,7 @@ impl HostPathDiscoveryEndpoint {
}
}
/// Initiate a peer discovery process through hostname lookup
/// Lookup a hostname
pub fn lookup(hostname: String) -> anyhow::Result<Self> {
Ok(Self {
addresses: ToSocketAddrs::to_socket_addrs(&hostname)?.collect(),
@@ -750,14 +478,10 @@ impl HostPathDiscoveryEndpoint {
})
}
/// List of address candidates for the peer
pub fn addresses(&self) -> &Vec<SocketAddr> {
&self.addresses
}
/// Calculates and stores the next value for [Self::scouting_state]
/// given the address and socket we just sent a scouting [crate::msgs::InitHello] message
/// to
fn insert_next_scout_offset(&self, srv: &AppServer, addr_no: usize, sock_no: usize) {
self.scouting_state.set((
(addr_no + 1) % self.addresses.len(),
@@ -812,11 +536,6 @@ impl HostPathDiscoveryEndpoint {
}
impl AppServer {
/// Construct a new AppServer
///
/// # Examples
///
/// See [Self].
pub fn new(
keypair: Option<(SSk, SPk)>,
addrs: Vec<SocketAddr>,
@@ -941,37 +660,22 @@ impl AppServer {
})
}
/// Access the cryptographic protocol server
///
/// This may return an error if [Self] was initialized without a keypair
/// and no keypair has been supplied since then.
///
/// I.e. will return an error if [Self::crypto_site] is not fully initialized
pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> {
self.crypto_site
.product_ref()
.context("Cryptography handler not initialized")
}
/// Access the cryptographic protocol server, mutably
///
/// This may return an error if [Self] was initialized without a keypair
/// and no keypair has been supplied since then.
///
/// I.e. will return an error if [Self::crypto_site] is not fully initialized
pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> {
self.crypto_site
.product_mut()
.context("Cryptography handler not initialized")
}
/// If set to [Verbosity::Verbose], then some extra information will be printed
/// at the info log level
pub fn verbose(&self) -> bool {
matches!(self.verbosity, Verbosity::Verbose)
}
/// Used by [Self::new] to register a new udp listen source
pub fn register_listen_socket(&mut self, mut sock: mio::net::UdpSocket) -> anyhow::Result<()> {
let mio_token = self.mio_token_dispenser.dispense();
self.mio_poll
@@ -983,19 +687,16 @@ impl AppServer {
Ok(())
}
/// Used to register a source of IO such as a listen socket with [Self::io_source_index]
pub fn register_io_source(&mut self, token: mio::Token, io_source: AppServerIoSource) {
let prev = self.io_source_index.insert(token, io_source);
assert!(prev.is_none());
}
/// Unregister an IO source registered with [Self::register_io_source]
pub fn unregister_io_source(&mut self, token: mio::Token) {
let value = self.io_source_index.remove(&token);
assert!(value.is_some(), "Removed IO source that does not exist");
}
/// Register a new WireGuard PSK broker
pub fn register_broker(
&mut self,
broker: Box<dyn WireguardBrokerMio<Error = anyhow::Error, MioError = anyhow::Error>>,
@@ -1018,7 +719,6 @@ impl AppServer {
Ok(BrokerStorePtr(ptr))
}
/// Unregister a WireGuard PSK broker registered with [Self::register_broker]
pub fn unregister_broker(&mut self, ptr: BrokerStorePtr) -> Result<()> {
let mut broker = self
.brokers
@@ -1030,11 +730,6 @@ impl AppServer {
Ok(())
}
/// Register a new protocol peer
///
/// # Examples
///
/// See [Self::new].
pub fn add_peer(
&mut self,
psk: Option<SymKey>,
@@ -1063,11 +758,6 @@ impl AppServer {
Ok(AppPeerPtr(pn))
}
/// Main IO handler; this generally does not terminate
///
/// # Examples
///
/// See [Self::new].
pub fn event_loop(&mut self) -> anyhow::Result<()> {
const INIT_SLEEP: f64 = 0.01;
const MAX_FAILURES: i32 = 10;
@@ -1121,9 +811,6 @@ impl AppServer {
}
}
/// IO handler without proactive restarts after errors.
///
/// This is used internally in [Self::event_loop].
pub fn event_loop_without_error_handling(&mut self) -> anyhow::Result<()> {
let (mut rx, mut tx) = (MsgBuf::zero(), MsgBuf::zero());
@@ -1243,8 +930,6 @@ impl AppServer {
}
}
/// Helper for [Self::event_loop_without_error_handling] to handle network messages
/// under DoS condition
fn handle_msg_under_load(
&mut self,
endpoint: &Endpoint,
@@ -1261,8 +946,6 @@ impl AppServer {
}
}
/// Used as a helper by [Self::event_loop_without_error_handling] when
/// a new output key has been echanged
pub fn output_key(
&mut self,
peer: AppPeerPtr,
@@ -1312,10 +995,6 @@ impl AppServer {
Ok(())
}
/// Poll for events from the cryptographic server ([Self::crypto_server()])
/// and for IO events through [Self::poll].
///
/// Used internally in [Self::event_loop_without_error_handling]
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
use crate::protocol::PollResult as C;
use AppPollResult as A;
@@ -1347,9 +1026,7 @@ impl AppServer {
Ok(res)
}
/// Tries to receive a new message from the network sockets.
///
/// Used internally in [Self::poll]
/// Tries to receive a new message
///
/// - might wait for an duration up to `timeout`
/// - returns immediately if an error occurs
@@ -1521,7 +1198,6 @@ impl AppServer {
Ok(None)
}
/// Internal helper for [Self::try_recv]
fn perform_mio_poll_and_register_events(&mut self, timeout: Duration) -> io::Result<()> {
self.mio_poll.poll(&mut self.events, Some(timeout))?;
// Fill the short poll buffer with the acquired events
@@ -1532,7 +1208,6 @@ impl AppServer {
Ok(())
}
/// Internal helper for [Self::try_recv]
fn try_recv_from_mio_token(
&mut self,
buf: &mut [u8],
@@ -1549,7 +1224,6 @@ impl AppServer {
self.try_recv_from_io_source(buf, io_source)
}
/// Internal helper for [Self::try_recv]
fn try_recv_from_io_source(
&mut self,
buf: &mut [u8],
@@ -1580,7 +1254,6 @@ impl AppServer {
}
}
/// Internal helper for [Self::try_recv]
fn try_recv_from_listen_socket(
&mut self,
buf: &mut [u8],
@@ -1616,20 +1289,6 @@ impl AppServer {
}
#[cfg(feature = "experiment_api")]
/// This is a wrapper around a reference to [AppServer] that
/// dishes out references to [AppServer::api_manager] and
/// to [AppServer] itself.
///
/// It really just implements [crate::api::mio::MioManagerContext] which
/// provides the methods operating on [crate::api::mio::MioManager] provided
/// through a trait.
///
/// This is a rather complicated way of providing just a few functions. The entire
/// point of this exercise is to decouple the code in the API from [AppServer] and
/// this file a bit, despite those functions all needing access to [AppServer].
///
/// We want the code to live in its own module instead of expanding and expanding the source
/// file with [AppServer] more and more.
struct MioManagerFocus<'a>(&'a mut AppServer);
#[cfg(feature = "experiment_api")]

View File

@@ -1,8 +1,3 @@
//! Contains the code used to parse command line parameters for rosenpass.
//!
//! [CliArgs::run] is called by the rosenpass main function and contains the
//! bulk of our boostrapping code while the main function just sets up the basic environment
use anyhow::{bail, ensure, Context};
use clap::{Parser, Subcommand};
use rosenpass_cipher_traits::Kem;
@@ -36,25 +31,15 @@ use {
std::thread,
};
/// How to reach a WireGuard PSK Broker
/// enum representing a choice of interface to a WireGuard broker
#[derive(Debug)]
pub enum BrokerInterface {
/// The PSK Broker is listening on a unix socket at the given path
Socket(PathBuf),
/// The PSK Broker broker is already connected to this process; a
/// unix socket stream can be reached at the given file descriptor.
///
/// This is generally used with file descriptor passing.
FileDescriptor(i32),
/// Create a socketpair(3p), spawn the PSK broker process from within rosenpass,
/// and hand one end of the socketpair to the broker process via file
/// descriptor passing to the subprocess
SocketPair,
}
/// Command line arguments to the Rosenpass binary.
///
/// Used for parsing with [clap].
/// struct holding all CLI arguments for `clap` crate to parse
#[derive(Parser, Debug)]
#[command(author, version, about, long_about, arg_required_else_help = true)]
pub struct CliArgs {
@@ -95,7 +80,6 @@ pub struct CliArgs {
#[arg(short, long, group = "psk-broker-specs")]
psk_broker_spawn: bool,
/// The subcommand to be invoked
#[command(subcommand)]
pub command: Option<CliCommand>,
@@ -114,10 +98,6 @@ pub struct CliArgs {
}
impl CliArgs {
/// Apply the command line parameters to the Rosenpass configuration struct
///
/// Generally the flow of control here is that all the command line parameters
/// are merged into the configuration file to avoid much code duplication.
pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> {
#[cfg(feature = "experiment_api")]
self.api.apply_to_config(_cfg)?;
@@ -143,11 +123,9 @@ impl CliArgs {
None
}
/// Return the WireGuard PSK broker interface configured.
///
/// Returns `None` if the `experiment_api` feature is disabled.
#[cfg(feature = "experiment_api")]
/// returns the broker interface set by CLI args
/// returns `None` if the `experiment_api` feature isn't enabled
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
if let Some(path_ref) = self.psk_broker_path.as_ref() {
Some(BrokerInterface::Socket(path_ref.to_path_buf()))
@@ -160,10 +138,9 @@ impl CliArgs {
}
}
/// Return the WireGuard PSK broker interface configured.
///
/// Returns `None` if the `experiment_api` feature is disabled.
#[cfg(not(feature = "experiment_api"))]
/// returns the broker interface set by CLI args
/// returns `None` if the `experiment_api` feature isn't enabled
pub fn get_broker_interface(&self) -> Option<BrokerInterface> {
None
}
@@ -267,17 +244,15 @@ pub enum CliCommand {
}
impl CliArgs {
/// Run Rosenpass with the given command line parameters
/// Runs the command specified via CLI
///
/// This contains the bulk of our startup logic with
/// the main function just setting up the basic environment
/// and then calling this function.
/// ## TODO
/// - This method consumes the [`CliCommand`] value. It might be wise to use a reference...
pub fn run(
self,
broker_interface: Option<BrokerInterface>,
test_helpers: Option<AppServerTest>,
) -> anyhow::Result<()> {
// TODO: This method consumes the [`CliCommand`] value. It might be wise to use a reference...
use CliCommand::*;
match &self.command {
Some(GenConfig { config_file, force }) => {
@@ -428,7 +403,6 @@ impl CliArgs {
Ok(())
}
/// Used by [Self::run] to start the Rosenpass key exchange server
fn event_loop(
config: config::Rosenpass,
broker_interface: Option<BrokerInterface>,
@@ -496,19 +470,6 @@ impl CliArgs {
srv.event_loop()
}
/// Create the WireGuard PSK broker to be used by
/// [crate::app_server::AppServer].
///
/// If the `experiment_api`
/// feature flag is set, then this communicates with a PSK broker
/// running in a different process as configured via
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
/// fields.
///
/// If the `experiment_api`
/// feature flag is not set, then this returns a [NativeUnixBroker],
/// sending pre-shared keys directly to WireGuard from within this
/// process.
#[cfg(feature = "experiment_api")]
fn create_broker(
broker_interface: Option<BrokerInterface>,
@@ -524,19 +485,6 @@ impl CliArgs {
}
}
/// Create the WireGuard PSK broker to be used by
/// [crate::app_server::AppServer].
///
/// If the `experiment_api`
/// feature flag is set, then this communicates with a PSK broker
/// running in a different process as configured via
/// the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
/// fields.
///
/// If the `experiment_api`
/// feature flag is not set, then this returns a [NativeUnixBroker],
/// sending pre-shared keys directly to WireGuard from within this
/// process.
#[cfg(not(feature = "experiment_api"))]
fn create_broker(
_broker_interface: Option<BrokerInterface>,
@@ -544,10 +492,6 @@ impl CliArgs {
Ok(Box::new(NativeUnixBroker::new()))
}
/// Used by [Self::create_broker] if the `experiment_api` is configured
/// to set up the connection with the PSK broker process as configured
/// via the `psk_broker_path`, `psk_broker_fd`, and `psk_broker_spawn`
/// fields.
#[cfg(feature = "experiment_api")]
fn get_broker_socket(broker_interface: BrokerInterface) -> Result<UnixStream, anyhow::Error> {
// Connect to the psk broker unix socket if one was specified
@@ -605,7 +549,7 @@ impl CliArgs {
}
/// generate secret and public keys, store in files according to the paths passed as arguments
pub fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
let mut ssk = crate::protocol::SSk::random();
let mut spk = crate::protocol::SPk::random();
StaticKem::keygen(ssk.secret_mut(), spk.deref_mut())?;

View File

@@ -4,9 +4,8 @@
//! [`Rosenpass`] which holds such a configuration.
//!
//! ## TODO
//! - TODO: support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
//! - TODO: provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
//! - support `~` in <https://github.com/rosenpass/rosenpass/issues/237>
//! - provide tooling to create config file from shell <https://github.com/rosenpass/rosenpass/issues/247>
use crate::protocol::{SPk, SSk};
use rosenpass_util::file::LoadValue;
use std::{
@@ -32,10 +31,7 @@ fn empty_api_config() -> crate::api::config::ApiConfig {
}
}
/// Configuration for the Rosenpass key exchange
///
/// i.e. configuration for the `rosenpass exchange` and `rosenpass exchange-config` commands
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize)]
pub struct Rosenpass {
// TODO: Raise error if secret key or public key alone is set during deserialization
// SEE: https://github.com/serde-rs/serde/issues/2793
@@ -50,10 +46,7 @@ pub struct Rosenpass {
/// list of [`SocketAddr`] to listen on
///
/// Examples:
///
/// - `0.0.0.0:123` Listen on any interface using IPv4, port 123
/// - `[::1]:1234` Listen on IPv6 localhost, port 1234
/// - `[::]:4476` Listen on any IPv4 or IPv6 interface, port 4476
/// - `0.0.0.0:123`
pub listen: Vec<SocketAddr>,
/// log verbosity
@@ -75,7 +68,6 @@ pub struct Rosenpass {
pub config_file_path: PathBuf,
}
/// Public key and secret key locations.
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
pub struct Keypair {
/// path to the public key file
@@ -86,7 +78,6 @@ pub struct Keypair {
}
impl Keypair {
/// Construct a keypair from its fields
pub fn new<Pk: AsRef<Path>, Sk: AsRef<Path>>(public_key: Pk, secret_key: Sk) -> Self {
let public_key = public_key.as_ref().to_path_buf();
let secret_key = secret_key.as_ref().to_path_buf();
@@ -97,72 +88,62 @@ impl Keypair {
}
}
/// Level of verbosity for [crate::app_server::AppServer]
///
/// The value of the field [crate::app_server::AppServer::verbosity]. See the field documentation
/// for details.
///
/// - TODO: replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
/// ## TODO
/// - replace this type with [`log::LevelFilter`], also see <https://github.com/rosenpass/rosenpass/pull/246>
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
pub enum Verbosity {
Quiet,
Verbose,
}
/// Configuration data for a single Rosenpass peer
/// ## TODO
/// - examples
/// - documentation
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RosenpassPeer {
/// path to the public key of the peer
pub public_key: PathBuf,
/// The hostname and port to connect to
///
/// Can be a
///
/// - hostname and port, e.g. `localhost:8876` or `rosenpass.eu:1427`
/// - IPv4 address and port, e.g. `1.2.3.4:7764`
/// - IPv6 address and port, e.g. `[fe80::24]:7890`
/// ## TODO
/// - documentation
pub endpoint: Option<String>,
/// path to the pre-shared key shared with the peer
/// path to the pre-shared key with the peer
///
/// NOTE: this item can be skipped in the config if you do not use a pre-shared key with the peer
pub pre_shared_key: Option<PathBuf>,
/// If this field is set to a path, the Rosenpass will write the exchanged symmetric keys
/// to the given file and write a notification to standard out to let the calling application
/// know that a new key was exchanged
/// ## TODO
/// - documentation
#[serde(default)]
pub key_out: Option<PathBuf>,
/// Information for supplying exchanged keys directly to WireGuard
/// ## TODO
/// - documentation
/// - make this field only available on binary builds, not on library builds <https://github.com/rosenpass/rosenpass/issues/249>
#[serde(flatten)]
pub wg: Option<WireGuard>,
}
/// Information for supplying exchanged keys directly to WireGuard
/// ## TODO
/// - documentation
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct WireGuard {
/// Name of the WireGuard interface to supply with pre-shared keys generated by the Rosenpass
/// key exchange
/// ## TODO
/// - documentation
pub device: String,
/// WireGuard public key of the peer to supply with pre-shared keys
/// ## TODO
/// - documentation
pub peer: String,
/// Extra parameters passed to the `wg` command
/// ## TODO
/// - documentation
#[serde(default)]
pub extra_params: Vec<String>,
}
impl Default for Rosenpass {
/// Generate an empty configuration
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
fn default() -> Self {
Self::empty()
}
@@ -175,15 +156,8 @@ impl Rosenpass {
/// checked whether they even exist.
///
/// ## TODO
///
/// - consider using a different algorithm to determine home directory the below one may
/// behave unexpectedly on Windows
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
pub fn load<P: AsRef<Path>>(p: P) -> anyhow::Result<Self> {
// read file and deserialize
let mut config: Self = toml::from_str(&fs::read_to_string(&p)?)?;
@@ -211,13 +185,7 @@ impl Rosenpass {
Ok(config)
}
/// Encode a configuration object as toml and write it to a file
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
/// Write a config to a file
pub fn store<P: AsRef<Path>>(&self, p: P) -> anyhow::Result<()> {
let serialized_config =
toml::to_string_pretty(&self).expect("unable to serialize the default config");
@@ -226,12 +194,6 @@ impl Rosenpass {
}
/// Commit the configuration to where it came from, overwriting the original file
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_store.rs")]
#[doc = "```"]
pub fn commit(&self) -> anyhow::Result<()> {
let mut f = fopen_w(&self.config_file_path, Visibility::Public)?;
f.write_all(toml::to_string_pretty(&self)?.as_bytes())?;
@@ -239,21 +201,13 @@ impl Rosenpass {
self.store(&self.config_file_path)
}
/// Apply the configuration in this object to the given [crate::app_server::AppServer]
pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> {
#[cfg(feature = "experiment_api")]
self.api.apply_to_app_server(_srv)?;
Ok(())
}
/// Check that the configuration is sound, ensuring
/// for instance that the referenced files exist
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
#[doc = "```"]
/// Validate a configuration
pub fn validate(&self) -> anyhow::Result<()> {
if let Some(ref keypair) = self.keypair {
// check the public key file exists
@@ -330,21 +284,6 @@ impl Rosenpass {
Ok(())
}
/// Check that the configuration is useful given the feature set Rosenpass was compiled with
/// and the configuration values.
///
/// This was introduced when we introduced a unix-socket API feature allowing the server
/// keypair to be supplied via the API; in this process we also made [Self::keypair] optional.
/// With respect to this particular feature, this function ensures that [Self::keypair] is set
/// when Rosenpass is compiles without the `experiment_api` flag. When `experiment_api` is
/// used, the function ensures that [Self::keypair] is only `None`, if the Rosenpass API is
/// enabled in the configuration.
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_validate.rs")]
#[doc = "```"]
pub fn check_usefullness(&self) -> anyhow::Result<()> {
#[cfg(not(feature = "experiment_api"))]
ensure!(self.keypair.is_some(), "Server keypair missing.");
@@ -360,38 +299,15 @@ impl Rosenpass {
Ok(())
}
/// Produce an empty confuguration
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
pub fn empty() -> Self {
Self::new(None)
}
/// Produce configuration from the keypair
///
/// Shorthand for calling [Self::new] with Some([Keypair]::new(sk, pk)).
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
pub fn from_sk_pk<Sk: AsRef<Path>, Pk: AsRef<Path>>(sk: Sk, pk: Pk) -> Self {
Self::new(Some(Keypair::new(pk, sk)))
}
/// Initialize a minimal configuration with the [Self::keypair] field supplied
/// as a parameter
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_new.rs")]
#[doc = "```"]
/// Creates a new configuration
pub fn new(keypair: Option<Keypair>) -> Self {
Self {
keypair,
@@ -405,14 +321,6 @@ impl Rosenpass {
}
/// Add IPv4 __and__ IPv6 IF_ANY address to the listen interfaces
///
/// I.e. listen on any interface.
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_add_if_any.rs")]
#[doc = "```"]
pub fn add_if_any(&mut self, port: u16) {
let ipv4_any = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), port));
let ipv6_any = SocketAddr::V6(SocketAddrV6::new(
@@ -425,20 +333,8 @@ impl Rosenpass {
self.listen.push(ipv6_any);
}
/// Parser for the old, IP style grammar.
///
/// See out manual page rosenpass-exchange(1) on details about the grammar.
///
/// This function parses the grammar and turns it into an instance of the configuration
/// struct.
///
/// TODO: the grammar is undecidable, what do we do here?
///
/// # Examples
///
#[doc = "```ignore"]
#[doc = include_str!("../tests/config_Rosenpass_parse_args_simple.rs")]
#[doc = "```"]
/// from chaotic args
/// Quest: the grammar is undecideable, what do we do here?
pub fn parse_args(args: Vec<String>) -> anyhow::Result<Self> {
let mut config = Self::new(Some(Keypair::new("", "")));
@@ -629,13 +525,11 @@ impl Rosenpass {
}
impl Default for Verbosity {
/// Self::Quiet
fn default() -> Self {
Self::Quiet
}
}
/// Example configuration generated by the command `rosenpass gen-config <TOML-FILE>`.
pub static EXAMPLE_CONFIG: &str = r###"public_key = "/path/to/rp-public-key"
secret_key = "/path/to/rp-secret-key"
listen = []
@@ -659,7 +553,7 @@ key_out = "/path/to/rp-key-out.txt" # path to store the key
mod test {
use super::*;
use std::borrow::Borrow;
use std::{borrow::Borrow, net::IpAddr};
fn toml_des<S: Borrow<str>>(s: S) -> Result<toml::Table, toml::de::Error> {
toml::from_str(s.borrow())
@@ -770,6 +664,37 @@ mod test {
Ok(())
}
#[test]
fn test_simple_cli_parse() {
let args = split_str(
"public-key /my/public-key secret-key /my/secret-key verbose \
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
peer.test:9999 outfile /peer/rp-out",
);
let config = Rosenpass::parse_args(args).unwrap();
assert_eq!(
config.keypair,
Some(Keypair::new("/my/public-key", "/my/secret-key"))
);
assert_eq!(config.verbosity, Verbosity::Verbose);
assert_eq!(
&config.listen,
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
);
assert_eq!(
config.peers,
vec![RosenpassPeer {
public_key: PathBuf::from("/peer/public-key"),
endpoint: Some("peer.test:9999".into()),
pre_shared_key: None,
key_out: Some(PathBuf::from("/peer/rp-out")),
..Default::default()
}]
)
}
#[test]
fn test_cli_parse_multiple_peers() {
let args = split_str(

View File

@@ -8,99 +8,29 @@ use thiserror::Error;
use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey};
#[derive(Debug, Clone)]
/// A pair of matching public/secret keys used to launch the crypto server.
///
/// # Examples
///
/// Decomposing a key pair into its individual components, then recreating it:
///
/// ```rust
/// use rosenpass::protocol::Keypair;
///
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// let random_pair = Keypair::random();
/// let random_copy = random_pair.clone();
/// let (sk_copy, pk_copy) = random_copy.into_parts();
///
/// // Re-assemble the key pair from the original secret/public key
/// // Note that it doesn't have to be the exact same keys;
/// // you could just as easily use a completely different pair here
/// let reconstructed_pair = Keypair::from_parts((sk_copy, pk_copy));
///
/// assert_eq!(random_pair.sk.secret(), reconstructed_pair.sk.secret());
/// assert_eq!(random_pair.pk, reconstructed_pair.pk);
/// ```
pub struct Keypair {
/// Secret key matching the crypto server's public key.
pub sk: SSk,
/// Public key identifying the crypto server instance.
pub pk: SPk,
}
// TODO: We need a named tuple derive
impl Keypair {
/// Creates a new key pair from the given secret/public key components.
///
/// # Example
///
/// ```rust
/// use rosenpass::protocol::{Keypair, SSk, SPk};
///
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// let random_sk = SSk::random();
/// let random_pk = SPk::random();
/// let random_pair = Keypair::new(random_sk.clone(), random_pk.clone());
///
/// assert_eq!(random_sk.secret(), random_pair.sk.secret());
/// assert_eq!(random_pk, random_pair.pk);
/// ```
pub fn new(sk: SSk, pk: SPk) -> Self {
Self { sk, pk }
}
/// Creates a new "empty" key pair. All bytes are initialized to zero.
///
/// See [SSk:zero()][crate::protocol::SSk::zero] and [SPk:zero()][crate::protocol::SPk::zero], respectively.
///
/// # Example
///
/// ```rust
/// use rosenpass::protocol::{Keypair, SSk, SPk};
///
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// let zero_sk = SSk::zero();
/// let zero_pk = SPk::zero();
/// let zero_pair = Keypair::zero();
///
/// assert_eq!(zero_sk.secret(), zero_pair.sk.secret());
/// assert_eq!(zero_pk, zero_pair.pk);
/// ```
pub fn zero() -> Self {
Self::new(SSk::zero(), SPk::zero())
}
/// Creates a new (securely-)random key pair. The mechanism is described in [rosenpass_secret_memory::Secret].
///
/// See [SSk:random()][crate::protocol::SSk::random] and [SPk:random()][crate::protocol::SPk::random], respectively.
pub fn random() -> Self {
Self::new(SSk::random(), SPk::random())
}
/// Creates a new key pair from the given public/secret key components.
pub fn from_parts(parts: (SSk, SPk)) -> Self {
Self::new(parts.0, parts.1)
}
/// Deconstructs the key pair, yielding the individual public/secret key components.
pub fn into_parts(self) -> (SSk, SPk) {
(self.sk, self.pk)
}
@@ -108,77 +38,25 @@ impl Keypair {
#[derive(Error, Debug)]
#[error("PSK already set in BuildCryptoServer")]
/// Error indicating that the PSK is already set.
/// Unused in the current version of the protocol.
pub struct PskAlreadySet;
#[derive(Error, Debug)]
#[error("Keypair already set in BuildCryptoServer")]
/// Error type indicating that the public/secret key pair has already been set.
pub struct KeypairAlreadySet;
#[derive(Error, Debug)]
#[error("Can not construct CryptoServer: Missing keypair")]
/// Error type indicating that no public/secret key pair has been provided.
pub struct MissingKeypair;
#[derive(Debug, Default)]
/// Builder for setting up a [CryptoServer] (with deferred initialization).
///
/// There are multiple ways of creating a crypto server:
///
/// 1. Provide the key pair at initialization time (using [CryptoServer::new][crate::protocol::CryptoServer::new])
/// 2. Provide the key pair at a later time (using [BuildCryptoServer::empty])
///
/// With BuildCryptoServer, you can gradually configure parameters as they become available.
/// This may be useful when they depend on runtime conditions or have to be fetched asynchronously.
/// It's possible to use the builder multiple times; it then serves as a "blueprint" for new
/// instances, several of which may be spawned with the same base configuration (or variations thereof).
///
/// Note that the server won't actually launch without a key pair (expect a [MissingKeypair] error).
/// The setup will be much simplified if one is provided, at the cost of some flexibility.
/// It's however possible to defer this step in case your application requires it.
///
/// For additional details or examples, see [AppServer::crypto_site][crate::app_server::AppServer::crypto_site] and [ConstructionSite][rosenpass_util::build::ConstructionSite].
///
/// # Example
///
/// ```rust
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, PeerParams, SPk, SymKey};
///
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// let keypair = Keypair::random();
/// let peer1 = PeerParams { psk: Some(SymKey::random()), pk: SPk::random() };
/// let peer2 = PeerParams { psk: None, pk: SPk::random() };
///
/// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![peer1]);
/// builder.add_peer(peer2.psk.clone(), peer2.pk);
///
/// let server = builder.build().expect("build failed");
/// assert_eq!(server.peers.len(), 2);
/// assert_eq!(server.sskm.secret(), keypair.sk.secret());
/// assert_eq!(server.spkm, keypair.pk);
/// ```
pub struct BuildCryptoServer {
/// The key pair (secret/public key) identifying the crypto server instance.
pub keypair: Option<Keypair>,
/// A list of network peers that should be registered when launching the server.
pub peers: Vec<PeerParams>,
}
impl Build<CryptoServer> for BuildCryptoServer {
type Error = anyhow::Error;
/// Creates a crypto server, adding all peers that have previously been registered.
///
/// You must provide a key pair at the time of instantiation.
/// If the list of peers is outdated, building the server will fail.
///
/// In this case, make sure to remove or re-add any peers that may have changed.
fn build(self) -> Result<CryptoServer, Self::Error> {
let Some(Keypair { sk, pk }) = self.keypair else {
return Err(MissingKeypair)?;
@@ -196,32 +74,20 @@ impl Build<CryptoServer> for BuildCryptoServer {
}
#[derive(Debug)]
/// Cryptographic key(s) identifying the connected [peer][crate::protocol::Peer] ("client")
/// for a given session that is being managed by the crypto server.
///
/// Each peer must be identified by a [public key (SPk)][crate::protocol::SPk].
/// Optionally, a [symmetric key (SymKey)][crate::protocol::SymKey]
/// can be provided when setting up the connection.
/// For more information on the intended usage and security considerations, see [Peer::psk][crate::protocol::Peer::psk] and [Peer::spkt][crate::protocol::Peer::spkt].
pub struct PeerParams {
/// Pre-shared (symmetric) encryption keys that should be used with this peer.
pub psk: Option<SymKey>,
/// Public key identifying the peer.
pub pk: SPk,
}
impl BuildCryptoServer {
/// Creates a new builder instance using the given key pair and peer list.
pub fn new(keypair: Option<Keypair>, peers: Vec<PeerParams>) -> Self {
Self { keypair, peers }
}
/// Creates an "incomplete" builder instance, without assigning a key pair.
pub fn empty() -> Self {
Self::new(None, Vec::new())
}
/// Creates a builder instance from the given key pair and peer list components.
pub fn from_parts(parts: (Option<Keypair>, Vec<PeerParams>)) -> Self {
Self {
keypair: parts.0,
@@ -229,165 +95,32 @@ impl BuildCryptoServer {
}
}
/// Deconstructs the current builder instance, taking ownership of its key pair and peer list.
///
/// Replaces all parameters with their default values, which allows extracting them
/// while leaving the builder in a reusable state.
pub fn take_parts(&mut self) -> (Option<Keypair>, Vec<PeerParams>) {
(self.keypair.take(), self.peers.swap_with_default())
}
/// Deconstructs the builder instance, yielding the assigned key pair and peer list.
pub fn into_parts(mut self) -> (Option<Keypair>, Vec<PeerParams>) {
self.take_parts()
}
/// Creates a new builder instance, assigning the given keypair to it.
///
/// Note that only one key pair can be assigned (expect [KeypairAlreadySet] on failure).
///
/// # Examples
///
/// ## Adding key pairs to an existing builder
///
/// ```rust
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair};
///
/// // Deferred initialization: Create builder first, add the key pair later
/// let mut builder = BuildCryptoServer::empty();
/// // Do something with the builder ...
///
/// // Quite some time may have passed (network/disk IO, runtime events, ...)
/// // Now we've got a key pair that should be added to the configuration
/// let keypair = Keypair::random();
/// builder.with_keypair(keypair.clone()).expect("build with key pair failed");
///
/// // New server instances can now make use of the assigned key pair
/// let server = builder.build().expect("build failed");
/// assert_eq!(server.sskm.secret(), keypair.sk.secret());
/// assert_eq!(server.spkm, keypair.pk);
/// ```
///
/// ## Basic error handling: Re-assigning key pairs
///
/// ```rust
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, KeypairAlreadySet};
///
/// // In this case, we'll create a functional builder from its various components
/// // These could be salvaged from another builder, or obtained from disk/network (etc.)
/// let keypair = Keypair::random();
/// let mut builder = BuildCryptoServer::from_parts((Some(keypair.clone()), Vec::new()));
///
/// // The builder has already been assigned a key pair, so this won't work
/// let err = builder.with_keypair(keypair).expect_err("should fail to reassign key pair");
/// assert!(matches!(err, KeypairAlreadySet));
/// ```
pub fn with_keypair(&mut self, keypair: Keypair) -> Result<&mut Self, KeypairAlreadySet> {
ensure_or(self.keypair.is_none(), KeypairAlreadySet)?;
self.keypair.insert(keypair).discard_result();
Ok(self)
}
/// Creates a new builder instance, adding a new entry to the list of registered peers.
///
/// # Example
///
/// Adding peers to an existing builder:
///
/// ```rust
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, SymKey, SPk};
///
/// // Deferred initialization: Create builder first, add some peers later
/// let keypair_option = Some(Keypair::random());
/// let mut builder = BuildCryptoServer::new(keypair_option, Vec::new());
/// assert!(builder.peers.is_empty());
///
/// // Do something with the builder ...
///
/// // Quite some time may have passed (network/disk IO, runtime events, ...)
/// // Now we've found a peer that should be added to the configuration
/// let pre_shared_key = SymKey::random();
/// let public_key = SPk::random();
/// builder.with_added_peer(Some(pre_shared_key.clone()), public_key.clone());
///
/// // New server instances will then start with the peer being registered already
/// let server = builder.build().expect("build failed");
/// assert_eq!(server.peers.len(), 1);
/// let peer = &server.peers[0];
/// let peer_psk = Some(peer.psk.clone()).expect("PSK is None");
/// assert_eq!(peer.spkt, public_key);
/// assert_eq!(peer_psk.secret(), pre_shared_key.secret());
/// ```
pub fn with_added_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> &mut Self {
// TODO: Check here already whether peer was already added
self.peers.push(PeerParams { psk, pk });
self
}
/// Add a new entry to the list of registered peers, with or without a pre-shared key.
pub fn add_peer(&mut self, psk: Option<SymKey>, pk: SPk) -> PeerPtr {
let id = PeerPtr(self.peers.len());
self.with_added_peer(psk, pk);
id
}
/// Creates a new builder, taking ownership of another instance's key pair and peer list.
/// Allows duplicating the current set of launch parameters, which can then be used to
/// start multiple servers with the exact same configuration (or variants using it as a base).
///
/// # Example
///
/// Extracting the server configuration from a builder:
///
/// ```rust
/// // We have to define the security policy before using Secrets.
/// use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
/// secret_policy_use_only_malloc_secrets();
///
/// use rosenpass_util::build::Build;
/// use rosenpass::protocol::{BuildCryptoServer, Keypair, SymKey, SPk};
///
/// let keypair = Keypair::random();
/// let peer_pk = SPk::random();
/// let mut builder = BuildCryptoServer::new(Some(keypair.clone()), vec![]);
/// builder.add_peer(None, peer_pk);
///
/// // Extract configuration parameters from the decomissioned builder
/// let (keypair_option, peers) = builder.take_parts();
/// let extracted_keypair = keypair_option.unwrap();
/// assert_eq!(extracted_keypair.sk.secret(), keypair.sk.secret());
/// assert_eq!(extracted_keypair.pk, keypair.pk);
/// assert_eq!(peers.len(), 1);
///
/// // Now we can create a new builder with the same configuration
/// let parts = (Some(extracted_keypair), peers);
/// let mut reassembled_builder = BuildCryptoServer::from_parts(parts);
/// let new_builder = reassembled_builder.emancipate();
///
/// // Do something with the new builder ...
///
/// // ... and now, deconstruct this one as well - still using the same parts
/// let (keypair_option, peers) = new_builder.into_parts();
/// let extracted_keypair = keypair_option.unwrap();
/// assert_eq!(extracted_keypair.sk.secret(), keypair.sk.secret());
/// assert_eq!(extracted_keypair.pk, keypair.pk);
/// assert_eq!(peers.len(), 1);
/// ```
pub fn emancipate(&mut self) -> Self {
Self::from_parts(self.take_parts())
}

View File

@@ -2899,7 +2899,7 @@ impl CryptoServer {
/// This is a lot of code. If you want to read the file outside of the documentation,
/// check out `rosenpass/tests/poll_example.rs" in the repository.
///
#[doc = "```ignore"]
#[doc = "```"]
#[doc = include_str!("../../tests/poll_example.rs")]
#[doc = "```"]
pub fn poll(&mut self) -> Result<PollResult> {

View File

@@ -22,17 +22,8 @@ struct KillChild(std::process::Child);
impl Drop for KillChild {
fn drop(&mut self) {
use rustix::process::{kill_process, Pid, Signal::Term};
let pid = Pid::from_child(&self.0);
// We seriously need to start handling signals with signalfd, our current signal handling
// system is a bit broken; there is probably a few functions that just restart on EINTR
// so the signal is absorbed
loop {
kill_process(pid, Term).discard_result();
if self.0.try_wait().unwrap().is_some() {
break;
}
}
self.0.kill().discard_result();
self.0.wait().discard_result()
}
}

View File

@@ -1,130 +0,0 @@
use std::{
net::SocketAddr,
ops::DerefMut,
path::PathBuf,
str::FromStr,
sync::mpsc,
thread::{self, sleep},
time::Duration,
};
use anyhow::ensure;
use rosenpass::{
app_server::{ipv4_any_binding, ipv6_any_binding, AppServer, AppServerTest, MAX_B64_KEY_SIZE},
protocol::{SPk, SSk, SymKey},
};
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_secret_memory::Secret;
use rosenpass_util::{file::LoadValueB64, functional::run, mem::DiscardResultExt, result::OkExt};
#[test]
fn key_exchange_with_app_server() -> anyhow::Result<()> {
let tmpdir = tempfile::tempdir()?;
let outfile_a = tmpdir.path().join("osk_a");
let outfile_b = tmpdir.path().join("osk_b");
// Set security policy for storing secrets; choose the one that is faster for testing
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
// Introduce the servers to each other
let psk_a = SymKey::random();
let psk_b = psk_a.clone();
let (tx_a, rx_b) = mpsc::sync_channel(1);
let (tx_b, rx_a) = mpsc::sync_channel(1);
let (tx_term_a, rx_term_a) = mpsc::channel();
let (tx_term_b, rx_term_b) = mpsc::channel();
let configs = [
(false, outfile_a.clone(), psk_a, tx_a, rx_a, rx_term_a),
(true, outfile_b.clone(), psk_b, tx_b, rx_b, rx_term_b),
];
for (is_client, osk, psk, tx, rx, rx_term) in configs {
thread::spawn(move || {
run(move || -> anyhow::Result<()> {
let mut srv = TestServer::new(rx_term)?;
tx.send((srv.loopback_port()?, srv.public_key()?.clone()))?;
let (otr_port, otr_pk) = rx.recv()?;
let psk = Some(psk);
let broker_peer = None;
let pk = otr_pk;
let outfile = Some(osk);
let port = otr_port;
let hostname = is_client.then(|| format!("[::1]:{port}"));
srv.app_srv
.add_peer(psk, pk, outfile, broker_peer, hostname)?;
srv.app_srv.event_loop()
})
.unwrap();
});
}
// Busy wait for both keys to be exchanged
let mut successful_exchange = false;
for _ in 0..2000 {
// 40s
sleep(Duration::from_millis(20));
run(|| -> anyhow::Result<()> {
let osk_a = SymKey::load_b64::<MAX_B64_KEY_SIZE, _>(&outfile_a)?;
let osk_b = SymKey::load_b64::<MAX_B64_KEY_SIZE, _>(&outfile_b)?;
successful_exchange = rosenpass_constant_time::memcmp(osk_a.secret(), osk_b.secret());
Ok(())
})
.discard_result();
if successful_exchange {
break;
}
}
// Tell the parties to terminate
tx_term_a.send(())?;
tx_term_b.send(())?;
assert!(
successful_exchange,
"Test did not complete successfully within the deadline"
);
Ok(())
}
struct TestServer {
app_srv: AppServer,
}
impl TestServer {
fn new(termination_queue: mpsc::Receiver<()>) -> anyhow::Result<Self> {
let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKem::keygen(sk.secret_mut(), pk.deref_mut())?;
let keypair = Some((sk, pk));
let addrs = vec![
SocketAddr::from_str("[::1]:0")?, // Localhost, any port. For connecting to the test server.
// ipv4_any_binding(), // any IPv4 interface
// ipv6_any_binding(), // any IPv6 interface
];
let verbosity = rosenpass::config::Verbosity::Verbose;
let test_helpers = Some(AppServerTest {
enable_dos_permanently: false,
termination_handler: Some(termination_queue),
});
let app_srv = AppServer::new(keypair, addrs, verbosity, test_helpers)?;
Self { app_srv }.ok()
}
fn loopback_port(&self) -> anyhow::Result<u16> {
self.app_srv.sockets[0].local_addr()?.port().ok()
}
fn public_key(&self) -> anyhow::Result<&SPk> {
Ok(&self.app_srv.crypto_server()?.spkm)
}
}

View File

@@ -1,10 +0,0 @@
use rosenpass::config::Rosenpass;
#[test]
fn config_Rosenpass_add_if_any_example() {
let mut v = Rosenpass::empty();
v.add_if_any(4000);
assert!(v.listen.iter().any(|a| format!("{a:?}") == "0.0.0.0:4000"));
assert!(v.listen.iter().any(|a| format!("{a:?}") == "[::]:4000"));
}

View File

@@ -1,18 +0,0 @@
use rosenpass::config::{Keypair, Rosenpass};
#[test]
fn example_config_rosenpass_new() {
let (sk, pk) = ("./example.sk", "./example.pk");
assert_eq!(Rosenpass::empty(), Rosenpass::new(None));
assert_eq!(Rosenpass::empty(), Rosenpass::default());
assert_eq!(
Rosenpass::from_sk_pk(sk, pk),
Rosenpass::new(Some(Keypair::new(pk, sk)))
);
let mut v = Rosenpass::empty();
v.keypair = Some(Keypair::new(pk, sk));
assert_eq!(Rosenpass::from_sk_pk(sk, pk), v);
}

View File

@@ -1,36 +0,0 @@
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
};
use rosenpass::config::{Keypair, Rosenpass, RosenpassPeer, Verbosity};
#[test]
fn parse_simple() {
let argv = "public-key /my/public-key secret-key /my/secret-key verbose \
listen 0.0.0.0:9999 peer public-key /peer/public-key endpoint \
peer.test:9999 outfile /peer/rp-out";
let argv = argv.split(' ').map(|s| s.to_string()).collect();
let config = Rosenpass::parse_args(argv).unwrap();
assert_eq!(
config.keypair,
Some(Keypair::new("/my/public-key", "/my/secret-key"))
);
assert_eq!(config.verbosity, Verbosity::Verbose);
assert_eq!(
&config.listen,
&vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 9999)]
);
assert_eq!(
config.peers,
vec![RosenpassPeer {
public_key: PathBuf::from("/peer/public-key"),
endpoint: Some("peer.test:9999".into()),
pre_shared_key: None,
key_out: Some(PathBuf::from("/peer/rp-out")),
..Default::default()
}]
);
}

View File

@@ -1,42 +0,0 @@
use std::path::PathBuf;
use rosenpass::config::{Rosenpass, Verbosity};
#[test]
fn example_config_rosenpass_store() -> anyhow::Result<()> {
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
let tmpdir = tempfile::tempdir()?;
let sk = tmpdir.path().join("example.sk");
let pk = tmpdir.path().join("example.pk");
let cfg = tmpdir.path().join("config.toml");
let mut c = Rosenpass::from_sk_pk(&sk, &pk);
// Can not commit config, path not known
assert!(c.commit().is_err());
// We can store it to an explicit path though
c.store(&cfg)?;
// Storing does not set commitment path
assert!(c.commit().is_err());
// We can reload the config now and the configurations
// are equal if we adjust the commitment path
let mut c2 = Rosenpass::load(&cfg)?;
c.config_file_path = PathBuf::from(&cfg);
assert_eq!(c, c2);
// And this loaded config can now be committed
c2.verbosity = Verbosity::Verbose;
c2.commit()?;
// And the changes actually made it to disk
let c3 = Rosenpass::load(cfg)?;
assert_eq!(c2, c3);
assert_ne!(c, c3);
Ok(())
}

View File

@@ -1,37 +0,0 @@
use std::fs;
use rosenpass::{cli::generate_and_save_keypair, config::Rosenpass};
#[test]
fn example_config_rosenpass_validate() -> anyhow::Result<()> {
rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets();
let tmpdir = tempfile::tempdir()?;
// Empty validates OK
assert!(Rosenpass::empty().validate().is_ok());
// Missing secret key does not pass usefulness
assert!(Rosenpass::empty().check_usefullness().is_err());
let sk = tmpdir.path().join("example.sk");
let pk = tmpdir.path().join("example.pk");
let cfg = Rosenpass::from_sk_pk(&sk, &pk);
// Missing secret key does not validate
assert!(cfg.validate().is_err());
// But passes usefulness (the configuration is useful but invalid)
assert!(cfg.check_usefullness().is_ok());
// Providing empty key files does not help
fs::write(&sk, b"")?;
fs::write(&pk, b"")?;
assert!(cfg.validate().is_err());
// But after providing proper key files, the configuration validates
generate_and_save_keypair(sk, pk)?;
assert!(cfg.validate().is_ok());
Ok(())
}

View File

@@ -1,15 +0,0 @@
use std::{borrow::Borrow, process::Command};
#[test]
fn test_gen_ipc_msg_types() -> anyhow::Result<()> {
let out = Command::new(env!("CARGO_BIN_EXE_rosenpass-gen-ipc-msg-types")).output()?;
assert!(out.status.success());
let stdout = String::from_utf8(out.stdout)?;
// Smoke tests only
assert!(stdout.contains("type RawMsgType = u128;"));
assert!(stdout.contains("const SUPPLY_KEYPAIR_RESPONSE : RawMsgType = RawMsgType::from_le_bytes(hex!(\"f2dc 49bd e261 5f10 40b7 3c16 ec61 edb9\"));"));
Ok(())
}

View File

@@ -1,50 +0,0 @@
use std::process::Command;
#[test]
fn smoketest() -> anyhow::Result<()> {
let tmpdir = tempfile::tempdir()?;
let secret = tmpdir.path().join("server.secret");
let public = tmpdir.path().join("server.public");
let invalid = tmpdir.path().join("invalid.secret");
let toml = tmpdir.path().join("config.toml");
let invalid_config = r#"
verbose = false
private_keys_dir = "invliad"
[[peers]]
public_keys_dir = "invliad"
"#;
// Generate keys
let status = Command::new(env!("CARGO_BIN_EXE_rp"))
.args(["genkey", secret.to_str().unwrap()])
.spawn()?
.wait()?;
assert!(status.success());
// Derive Public keys
let status = Command::new(env!("CARGO_BIN_EXE_rp"))
.args(["pubkey", secret.to_str().unwrap(), public.to_str().unwrap()])
.spawn()?
.wait()?;
assert!(status.success());
// Can not exchange keys using exchange with invalid keys
let out = Command::new(env!("CARGO_BIN_EXE_rp"))
.args(["exchange", invalid.to_str().unwrap()])
.output()?;
assert!(!out.status.success());
std::fs::write(toml, invalid_config)?;
let out = Command::new(env!("CARGO_BIN_EXE_rp"))
.args([
"exchange-config",
tmpdir.path().join("invalid_config").to_str().unwrap(),
])
.output()?;
assert!(!out.status.success());
Ok(())
}

View File

@@ -1,9 +1,3 @@
//! This module provides a wrapper [MallocAllocator] around the memsec allocator in
//! [memsec]. The wrapper implements the [Allocator] trait and thus makes the memsec allocator
//! usable as a drop-in replacement wherever the [Allocator] trait is required.
//!
//! The module also provides the [MallocVec] and [MallocBox] types.
use std::fmt;
use std::ptr::NonNull;
@@ -12,78 +6,31 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
#[derive(Copy, Clone, Default)]
struct MallocAllocatorContents;
/// A wrapper around the memsec allocator in [memsec] that implements the [Allocator] trait from
/// the [allocator_api2] crate.
/// Memory allocation using using the memsec crate
#[derive(Copy, Clone, Default)]
pub struct MallocAllocator {
_dummy_private_data: MallocAllocatorContents,
}
/// A [allocator_api2::boxed::Box] backed by the memsec allocator
/// from the [memsec] crate.
/// A box backed by the memsec allocator
pub type MallocBox<T> = allocator_api2::boxed::Box<T, MallocAllocator>;
/// A [allocator_api2::vec::Vec] backed by the memsec allocator
/// from the [memsec] crate.
/// A vector backed by the memsec allocator
pub type MallocVec<T> = allocator_api2::vec::Vec<T, MallocAllocator>;
/// Try to allocate a [MallocBox] for the type `T`. If `T` is zero-sized the allocation
/// still works. It returns an error if the allocation fails.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_box_try, MallocBox};
/// let data: u8 = 42;
/// let malloc_box: MallocBox<u8> = malloc_box_try(data)?;
/// # assert_eq!(*malloc_box, 42u8);
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn malloc_box_try<T>(x: T) -> Result<MallocBox<T>, AllocError> {
MallocBox::<T>::try_new_in(x, MallocAllocator::new())
}
/// Allocate a [MallocBox] for the type `T`. If `T` is zero-sized the allocation
/// still works.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_box, MallocBox};
/// let data: u8 = 42;
/// let malloc_box: MallocBox<u8> = malloc_box(data);
/// # assert_eq!(*malloc_box, 42u8);
/// ```
pub fn malloc_box<T>(x: T) -> MallocBox<T> {
MallocBox::<T>::new_in(x, MallocAllocator::new())
}
/// Allocate a [MallocVec] for the type `T`. No memory will be actually allocated
/// until elements are pushed to the vector.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::malloc::{malloc_vec, MallocVec};
/// let mut malloc_vec: MallocVec<u8> = malloc_vec();
/// malloc_vec.push(0u8);
/// malloc_vec.push(1u8);
/// malloc_vec.push(2u8);
/// # let mut element = malloc_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 2u8);
/// # element = malloc_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 1u8);
/// # element = malloc_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 0u8);
/// # element = malloc_vec.pop();
/// # assert!(element.is_none());
/// ```
pub fn malloc_vec<T>() -> MallocVec<T> {
MallocVec::<T>::new_in(MallocAllocator::new())
}
impl MallocAllocator {
/// Creates a new [MallocAllocator].
pub fn new() -> Self {
Self {
_dummy_private_data: MallocAllocatorContents,
@@ -147,9 +94,6 @@ mod test {
malloc_allocation_impl::<8>(&alloc);
malloc_allocation_impl::<64>(&alloc);
malloc_allocation_impl::<999>(&alloc);
// Also test the debug-print for good measure
let _ = format!("{:?}", alloc);
}
fn malloc_allocation_impl<const N: usize>(alloc: &MallocAllocator) {

View File

@@ -1,9 +1,3 @@
//! This module provides a wrapper [MemfdSecAllocator] around the memfdsec allocator in
//! [memsec]. The wrapper implements the [Allocator] trait and thus makes the memfdsec allocator
//! usable as a drop-in replacement wherever the [Allocator] trait is required.
//!
//! The module also provides the [MemfdSecVec] and [MemfdSecBox] types.
#![cfg(target_os = "linux")]
use std::fmt;
use std::ptr::NonNull;
@@ -13,78 +7,31 @@ use allocator_api2::alloc::{AllocError, Allocator, Layout};
#[derive(Copy, Clone, Default)]
struct MemfdSecAllocatorContents;
/// A wrapper around the memfdsec allocator in [memsec] that implements the [Allocator] trait from
/// the [allocator_api2] crate.
/// Memory allocation using using the memsec crate
#[derive(Copy, Clone, Default)]
pub struct MemfdSecAllocator {
_dummy_private_data: MemfdSecAllocatorContents,
}
/// A [allocator_api2::boxed::Box] backed by the memfdsec allocator
/// from the [memsec] crate.
/// A box backed by the memsec allocator
pub type MemfdSecBox<T> = allocator_api2::boxed::Box<T, MemfdSecAllocator>;
/// A [allocator_api2::vec::Vec] backed by the memfdsec allocator
/// from the [memsec] crate.
/// A vector backed by the memsec allocator
pub type MemfdSecVec<T> = allocator_api2::vec::Vec<T, MemfdSecAllocator>;
/// Try to allocate a [MemfdSecBox] for the type `T`. If `T` is zero-sized the allocation
/// still works. It returns an error if the allocation fails.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_box_try, MemfdSecBox};
/// let data: u8 = 42;
/// let memfdsec_box: MemfdSecBox<u8> = memfdsec_box_try(data)?;
/// # assert_eq!(*memfdsec_box, 42u8);
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn memfdsec_box_try<T>(x: T) -> Result<MemfdSecBox<T>, AllocError> {
MemfdSecBox::<T>::try_new_in(x, MemfdSecAllocator::new())
}
/// Allocate a [MemfdSecBox] for the type `T`. If `T` is zero-sized the allocation
/// still works.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_box, MemfdSecBox};
/// let data: u8 = 42;
/// let memfdsec_box: MemfdSecBox<u8> = memfdsec_box(data);
/// # assert_eq!(*memfdsec_box, 42u8);
/// ```
pub fn memfdsec_box<T>(x: T) -> MemfdSecBox<T> {
MemfdSecBox::<T>::new_in(x, MemfdSecAllocator::new())
}
/// Allocate a [MemfdSecVec] for the type `T`. No memory will be actually allocated
/// until elements are pushed to the vector.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::memsec::memfdsec::{memfdsec_vec, MemfdSecVec};
/// let mut memfdsec_vec: MemfdSecVec<u8> = memfdsec_vec();
/// memfdsec_vec.push(0u8);
/// memfdsec_vec.push(1u8);
/// memfdsec_vec.push(2u8);
/// # let mut element = memfdsec_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 2u8);
/// # element = memfdsec_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 1u8);
/// # element = memfdsec_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 0u8);
/// # element = memfdsec_vec.pop();
/// # assert!(element.is_none());
/// ```
pub fn memfdsec_vec<T>() -> MemfdSecVec<T> {
MemfdSecVec::<T>::new_in(MemfdSecAllocator::new())
}
impl MemfdSecAllocator {
/// Create a new [MemfdSecAllocator].
pub fn new() -> Self {
Self {
_dummy_private_data: MemfdSecAllocatorContents,
@@ -148,9 +95,6 @@ mod test {
memfdsec_allocation_impl::<8>(&alloc);
memfdsec_allocation_impl::<64>(&alloc);
memfdsec_allocation_impl::<999>(&alloc);
// Also test the debug-print for good measure
let _ = format!("{:?}", alloc);
}
fn memfdsec_allocation_impl<const N: usize>(alloc: &MemfdSecAllocator) {

View File

@@ -1,6 +1,2 @@
//! This module provides wrappers around the memfdsec and the memsec allocators from the
//! [memsec] crate. The wrappers implement the [Allocator](allocator_api2::alloc::Allocator) trait
//! and can thus be used as a drop in replacement wherever ever this trait is required.
pub mod malloc;
pub mod memfdsec;

View File

@@ -1,12 +1,3 @@
//! This module provides a [SecretAllocator](SecretAlloc) that allocates memory with extra
//! protections that make it more difficult for threat actors to access secrets that they aren't
//! authorized to access. At the moment the `memsec` and `memfdsec` allocators from the
//! [memsec] crate are supported for this purpose.
//!
//! [SecretAlloc] implements the [Allocator] trait and can thus be used as a drop in replacement
//! wherever ever this trait is required.
//!
//! The module also provides the [SecretVec] and [SecretBox] types.
pub mod memsec;
use std::sync::OnceLock;
@@ -17,50 +8,15 @@ use memsec::malloc::MallocAllocator;
#[cfg(target_os = "linux")]
use memsec::memfdsec::MemfdSecAllocator;
/// Globally configures which [SecretAllocType] to use as default for
/// [SecretAllocators](SecretAlloc).
static ALLOC_TYPE: OnceLock<SecretAllocType> = OnceLock::new();
/// Sets the secret allocation type to use by default for [SecretAllocators](SecretAlloc).
/// It is intended that this function is called at startup before a secret allocation
/// takes place.
///
/// # Example
/// ```rust
/// # use std::alloc::Layout;
/// # use allocator_api2::alloc::Allocator;
/// # use rosenpass_secret_memory::alloc::{set_secret_alloc_type, SecretAlloc, SecretAllocType};
/// set_secret_alloc_type(SecretAllocType::MemsecMalloc);
/// let secret_alloc = SecretAlloc::default();
/// unsafe {
/// let memory = secret_alloc.allocate(Layout::from_size_align_unchecked(128, 32))?;
/// }
/// # Ok::<(), anyhow::Error>(())
/// ```
/// ```
/// Sets the secret allocation type to use.
/// Intended usage at startup before secret allocation
/// takes place
pub fn set_secret_alloc_type(alloc_type: SecretAllocType) {
ALLOC_TYPE.set(alloc_type).unwrap();
}
/// Initializes type of allocator to be sued with `alloc_type` if it is not initialized yet. Returns
/// the current [SecretAllocType] afterward.
///
/// # Example
/// ```rust
/// # use std::alloc::Layout;
/// # use allocator_api2::alloc::Allocator;
/// # use rosenpass_secret_memory::alloc::{get_or_init_secret_alloc_type, set_secret_alloc_type,
/// # SecretAlloc, SecretAllocType};
/// set_secret_alloc_type(SecretAllocType::MemsecMalloc);
/// #[cfg(target_os = "linux")] {
/// let alloc_typpe = get_or_init_secret_alloc_type(SecretAllocType::MemsecMemfdSec);
/// assert_eq!(alloc_typpe, SecretAllocType::MemsecMalloc);
/// }
/// #[cfg(not(target_os = "linux"))] {
/// let alloc_typpe = get_or_init_secret_alloc_type(SecretAllocType::MemsecMalloc);
/// assert_eq!(alloc_typpe, SecretAllocType::MemsecMalloc);
/// }
///```
pub fn get_or_init_secret_alloc_type(alloc_type: SecretAllocType) -> SecretAllocType {
*ALLOC_TYPE.get_or_init(|| alloc_type)
}
@@ -72,7 +28,6 @@ pub enum SecretAllocType {
MemsecMemfdSec,
}
/// An [Allocator] that uses a [SecretAllocType] for allocation.
pub struct SecretAlloc {
alloc_type: SecretAllocType,
}
@@ -113,72 +68,19 @@ unsafe impl Allocator for SecretAlloc {
}
}
/// A [allocator_api2::boxed::Box] that is backed by [SecretAlloc].
pub type SecretBox<T> = allocator_api2::boxed::Box<T, SecretAlloc>;
/// A [allocator_api2::vec::Vec] that is backed by [SecretAlloc].
/// A vector backed by the memsec allocator
pub type SecretVec<T> = allocator_api2::vec::Vec<T, SecretAlloc>;
/// Try to allocate a [SecretBox] for the type `T`. If `T` is zero-sized the allocation
/// still works. It returns an error if the allocation fails.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::{secret_box_try, SecretBox};
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
/// use rosenpass_secret_memory::alloc::set_secret_alloc_type;
/// set_secret_alloc_type(MemsecMalloc);
/// let data: u8 = 42;
/// let secret_box: SecretBox<u8> = secret_box_try(data)?;
/// # assert_eq!(*secret_box, 42u8);
/// # Ok::<(), anyhow::Error>(())
/// ```
pub fn secret_box_try<T>(x: T) -> Result<SecretBox<T>, AllocError> {
SecretBox::<T>::try_new_in(x, SecretAlloc::default())
}
/// Allocates a [SecretBox] for the type `T`. If `T` is zero-sized the allocation
/// still works.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::{secret_box, SecretBox};
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
/// # use rosenpass_secret_memory::alloc::set_secret_alloc_type;
/// set_secret_alloc_type(MemsecMalloc);
/// let data: u8 = 42;
/// let secret_box: SecretBox<u8> = secret_box(data);
/// # assert_eq!(*secret_box, 42u8);
/// ```
pub fn secret_box<T>(x: T) -> SecretBox<T> {
SecretBox::<T>::new_in(x, SecretAlloc::default())
}
/// Allocate a [SecretVec] for the type `T`. No memory will be actually allocated
/// until elements are pushed to the vector.
///
/// # Example
/// ```rust
/// # use rosenpass_secret_memory::alloc::{secret_vec, SecretVec};
/// # use rosenpass_secret_memory::alloc::SecretAllocType::MemsecMalloc;
/// # use rosenpass_secret_memory::alloc::set_secret_alloc_type;
/// set_secret_alloc_type(MemsecMalloc);
/// let mut secret_vec: SecretVec<u8> = secret_vec();
/// secret_vec.push(0u8);
/// secret_vec.push(1u8);
/// secret_vec.push(2u8);
/// # let mut element = secret_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 2u8);
/// # element = secret_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 1u8);
/// # element = secret_vec.pop();
/// # assert!(element.is_some());
/// # assert_eq!(element.unwrap(), 0u8);
/// # element = secret_vec.pop();
/// # assert!(element.is_none());
/// ```
pub fn secret_vec<T>() -> SecretVec<T> {
SecretVec::<T>::new_in(SecretAlloc::default())
}

View File

@@ -1,31 +1,6 @@
//! This module provides a helper for creating debug prints for byte slices.
//! See [debug_crypto_array] for more details.
use std::fmt;
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter].
/// # Example
///
/// ```rust
/// use std::fmt::{Debug, Formatter};
/// use rosenpass_secret_memory::debug::debug_crypto_array;
///
/// struct U8Wrapper {
/// pub u_eigt: Vec<u8>
/// }
/// impl Debug for U8Wrapper {fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
/// // let dead_beef: [u8; 11] = [3, 3, 6, 5, 3, 3, 3, 7, 3, 5, 7];
/// debug_crypto_array(self.u_eigt.as_slice(), f)
/// }
/// }
/// // Short byte slices are printed completely.
/// let cafe = U8Wrapper {u_eigt: vec![1, 4, 5, 3, 7, 6]};
/// assert_eq!(format!("{:?}", cafe), "[{}]=145376");
/// // For longer byte slices, only the first 32 and last 32 bytes are printed.
/// let all_u8 = U8Wrapper {u_eigt: (0..256).map(|i| i as u8).collect()};
/// assert_eq!(format!("{:?}", all_u8), "[{}]=0123456789abcdef101112131415161718191a1b1c1d1e1f…e0e\
/// 1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
/// ```
/// Writes the contents of an `&[u8]` as hexadecimal symbols to a [std::fmt::Formatter]
pub fn debug_crypto_array(v: &[u8], fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str("[{}]=")?;
if v.len() > 64 {

View File

@@ -1,46 +1,8 @@
//! Objects that implement this Trait provide a way to store their data in way that respects the
//! confidentiality of its data. Specifically, an object implementing this Trait guarantees
//! if its data with [store_secret](StoreSecret::store_secret) are saved in the file with visibility
//! equivalent to [rosenpass_util::file::Visibility::Secret].
use std::path::Path;
/// Objects that implement this Trait provide a standard method to be stored securely. The trait can
/// be implemented as follows for example:
/// # Example
/// ```rust
/// use std::io::Write;
/// use std::path::Path;
/// use rosenpass_secret_memory::file::StoreSecret;
///
/// use rosenpass_util::file::{fopen_w, Visibility};
///
/// struct MyWeirdI32 {
/// _priv_i32: [u8; 4],
/// }
///
/// impl StoreSecret for MyWeirdI32 {
/// type Error = std::io::Error;
///
/// fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
/// fopen_w(path, Visibility::Secret)?.write_all(&self._priv_i32)?;
/// Ok(())
/// }
///
/// fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error> {
/// fopen_w(path, Visibility::Public)?.write_all(&self._priv_i32)?;
/// Ok(())
/// }
/// }
/// ```
pub trait StoreSecret {
type Error;
/// Stores the object securely. In particular, it ensures that the visibility is equivalent to
/// [rosenpass_util::file::Visibility::Secret].
fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
/// Stores the object. No requirement on the visibility is given, but it is common to store
/// the data with visibility equivalent to [rosenpass_util::file::Visibility::Public].
fn store<P: AsRef<Path>>(&self, path: P) -> Result<(), Self::Error>;
}

View File

@@ -1,38 +1,3 @@
//! This library provides functionality for working with secret data and protecting it in
//! memory from illegitimate access.
//!
//! Specifically, the [alloc] module provides wrappers around the `memsec` and `memfdsec` allocators
//! from the [memsec] crate that implement the [Allocator](allocator_api2::alloc::Allocator) Trait.
//! We refer to the documentation of these modules for more details on their appropriate usage.
//!
//! The [policy] module then provides functionality for specifying which of the allocators from
//! the [alloc] module should be used.
//!
//! Once this configuration is made [Secret] can be used to store sensitive data in memory
//! allocated by the configured allocator. [Secret] is implemented such that memory is *aloways*
//! zeroized before it is released. Because allocations of the protected memory are expensive to do,
//! [Secret] is build to reuse once allocated memory. A simple use of [Secret] looks as follows:
//! # Exmaple
//! ```rust
//! use zeroize::Zeroize;
//! use rosenpass_secret_memory::{secret_policy_try_use_memfd_secrets, Secret};
//! secret_policy_try_use_memfd_secrets();
//! let mut my_secret: Secret<32> = Secret::random();
//! my_secret.zeroize();
//! ```
//!
//! # Futher functionality
//! In addition to this core functionality, this library provides some more smaller tools.
//!
//! 1. [Public] and [PublicBox] provide byte array storage for public data in a manner analogous to
//! that of [Secret].
//! 2. The [debug] module provides functionality to easily create debug output for objects that are
//! backed by byte arrays or slices, like for example [Secret].
//! 3. The [mod@file] module provides functionality to store [Secrets](crate::Secret)
//! and [Public] in files such that the file's [Visibility](rosenpass_util::file::Visibility)
//! corresponds to the confidentiality of the data.
//! 4. The [rand] module provides a simple way of generating randomness.
pub mod debug;
pub mod file;
pub mod rand;

View File

@@ -1,12 +1,3 @@
//! This crates the `memsec` and `memfdsec` allocators from the [memsec] crate to be used for
//! allocations of memory on which [Secrects](crate::Secret) are stored. This, however, requires
//! that an allocator is chosen before [Secret](crate::Secret) is used the first time.
//! This module provides functionality for just that.
/// This function sets the `memfdsec` allocator as the default in case it is supported by
/// the target and uses the `memsec` allocator otherwise.
///
/// At the time of writing, the `memfdsec` allocator is just supported on linux targets.
pub fn secret_policy_try_use_memfd_secrets() {
let alloc_type = {
#[cfg(target_os = "linux")]
@@ -31,8 +22,6 @@ pub fn secret_policy_try_use_memfd_secrets() {
log::info!("Secrets will be allocated using {:?}", alloc_type);
}
/// This functions sets the `memfdsec` allocator as the default. At the time of writing
/// this is only supported on Linux targets.
#[cfg(target_os = "linux")]
pub fn secret_policy_use_only_memfd_secrets() {
let alloc_type = crate::alloc::SecretAllocType::MemsecMemfdSec;
@@ -45,7 +34,6 @@ pub fn secret_policy_use_only_memfd_secrets() {
log::info!("Secrets will be allocated using {:?}", alloc_type);
}
/// This function sets the `memsec` allocator as the default. It is supported on all targets.
pub fn secret_policy_use_only_malloc_secrets() {
let alloc_type = crate::alloc::SecretAllocType::MemsecMalloc;
assert_eq!(

View File

@@ -15,21 +15,7 @@ use std::ops::{Deref, DerefMut};
use std::path::Path;
/// Contains information in the form of a byte array that may be known to the
/// public.
///
/// # Example
/// ```rust
/// # use zeroize::Zeroize;
/// # use rosenpass_secret_memory::{Public};
///
/// let mut my_public_data: Public<32> = Public::random();
/// // Fill with some random data that I can use a cryptographic key later on.
/// my_public_data.randomize();
/// // A Public can be overwritten with zeros.
/// my_public_data.zeroize();
/// // If a Public is printed as Debug, its content is printed byte for byte.
/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000");
/// ```
/// public
// TODO: We should get rid of the Public type; just use a normal value
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
@@ -38,84 +24,75 @@ pub struct Public<const N: usize> {
}
impl<const N: usize> Public<N> {
/// Create a new [Public] from a byte slice.
/// Create a new [Public] from a byte slice
pub fn from_slice(value: &[u8]) -> Self {
copy_slice(value).to_this(Self::zero)
}
/// Create a new [Public] from a byte array.
/// Create a new [Public] from a byte array
pub fn new(value: [u8; N]) -> Self {
Self { value }
}
/// Create a zero initialized [Public].
/// Create a zero initialized [Public]
pub fn zero() -> Self {
Self { value: [0u8; N] }
}
/// Create a random initialized [Public].
/// Create a random initialized [Public]
pub fn random() -> Self {
mutating(Self::zero(), |r| r.randomize())
}
/// Randomize all bytes in an existing [Public].
/// Randomize all bytes in an existing [Public]
pub fn randomize(&mut self) {
self.try_fill(&mut crate::rand::rng()).unwrap()
}
}
impl<const N: usize> Randomize for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
self.value.try_fill(rng)
}
}
impl<const N: usize> fmt::Debug for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
debug_crypto_array(&self.value, fmt)
}
}
impl<const N: usize> Deref for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Target = [u8; N];
// No extra documentation here because the Trait already provides a good documentation.
fn deref(&self) -> &[u8; N] {
&self.value
}
}
impl<const N: usize> DerefMut for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn deref_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
impl<const N: usize> Borrow<[u8; N]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow(&self) -> &[u8; N] {
&self.value
}
}
impl<const N: usize> BorrowMut<[u8; N]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow_mut(&mut self) -> &mut [u8; N] {
&mut self.value
}
}
impl<const N: usize> Borrow<[u8]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow(&self) -> &[u8] {
&self.value
}
}
impl<const N: usize> BorrowMut<[u8]> for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow_mut(&mut self) -> &mut [u8] {
&mut self.value
}
@@ -124,7 +101,6 @@ impl<const N: usize> BorrowMut<[u8]> for Public<N> {
impl<const N: usize> LoadValue for Public<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
fopen_r(path)?.read_exact_to_end(&mut *v)?;
@@ -135,7 +111,6 @@ impl<const N: usize> LoadValue for Public<N> {
impl<const N: usize> StoreValue for Public<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, **self)?;
Ok(())
@@ -143,10 +118,8 @@ impl<const N: usize> StoreValue for Public<N> {
}
impl<const N: usize> LoadValueB64 for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
where
Self: Sized,
@@ -169,7 +142,6 @@ impl<const N: usize> LoadValueB64 for Public<N> {
impl<const N: usize> StoreValueB64 for Public<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
let p = path.as_ref();
let mut f = [0u8; F];
@@ -183,10 +155,8 @@ impl<const N: usize> StoreValueB64 for Public<N> {
}
impl<const N: usize> StoreValueB64Writer for Public<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64_writer<const F: usize, W: std::io::Write>(
&self,
mut writer: W,
@@ -202,117 +172,89 @@ impl<const N: usize> StoreValueB64Writer for Public<N> {
}
}
/// A [Box] around a [Public] so that the latter one can be allocated on the heap.
///
/// # Example
/// ```rust
/// # use zeroize::Zeroize;
/// # use rosenpass_secret_memory::{Public, PublicBox};
///
/// let mut my_public_data: Public<32> = Public::random();
/// let mut my_bbox: PublicBox<32> = PublicBox{ inner: Box::new(my_public_data)};
///
/// // Now we can practically handle it just as we would handle the Public itself:
/// // Fill with some random data that I can use a cryptographic key later on.
/// my_public_data.randomize();
/// // A Public can be overwritten with zeros.
/// my_public_data.zeroize();
/// // If a Public is printed as Debug, its content is printed byte for byte.
/// assert_eq!(format!("{:?}", my_public_data), "[{}]=00000000000000000000000000000000");
/// ```
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct PublicBox<const N: usize> {
/// The inner [Box] around the [Public].
pub inner: Box<Public<N>>,
}
impl<const N: usize> PublicBox<N> {
/// Create a new [PublicBox] from a byte slice.
/// Create a new [PublicBox] from a byte slice
pub fn from_slice(value: &[u8]) -> Self {
Self {
inner: Box::new(Public::from_slice(value)),
}
}
/// Create a new [PublicBox] from a byte array.
/// Create a new [PublicBox] from a byte array
pub fn new(value: [u8; N]) -> Self {
Self {
inner: Box::new(Public::new(value)),
}
}
/// Create a zero initialized [PublicBox].
/// Create a zero initialized [PublicBox]
pub fn zero() -> Self {
Self {
inner: Box::new(Public::zero()),
}
}
/// Create a random initialized [PublicBox].
/// Create a random initialized [PublicBox]
pub fn random() -> Self {
Self {
inner: Box::new(Public::random()),
}
}
/// Randomize all bytes in an existing [PublicBox].
/// Randomize all bytes in an existing [PublicBox]
pub fn randomize(&mut self) {
self.inner.randomize()
}
}
impl<const N: usize> Randomize for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
self.inner.try_fill(rng)
}
}
impl<const N: usize> fmt::Debug for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
debug_crypto_array(&**self, fmt)
}
}
impl<const N: usize> Deref for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Target = [u8; N];
// No extra documentation here because the Trait already provides a good documentation.
fn deref(&self) -> &[u8; N] {
self.inner.deref()
}
}
impl<const N: usize> DerefMut for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn deref_mut(&mut self) -> &mut [u8; N] {
self.inner.deref_mut()
}
}
impl<const N: usize> Borrow<[u8]> for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow(&self) -> &[u8] {
self.deref()
}
}
impl<const N: usize> BorrowMut<[u8]> for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn borrow_mut(&mut self) -> &mut [u8] {
self.deref_mut()
}
}
impl<const N: usize> LoadValue for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// This is implemented separately from Public to avoid allocating too much stack memory
// No extra documentation here because the Trait already provides a good documentation.
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut p = Self::random();
fopen_r(path)?.read_exact_to_end(p.deref_mut())?;
@@ -321,26 +263,22 @@ impl<const N: usize> LoadValue for PublicBox<N> {
}
impl<const N: usize> StoreValue for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
self.inner.store(path)
}
}
impl<const N: usize> LoadValueB64 for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// This is implemented separately from Public to avoid allocating too much stack memory.
// No extra documentation here because the Trait already provides a good documentation.
// This is implemented separately from Public to avoid allocating too much stack memory
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> Result<Self, Self::Error>
where
Self: Sized,
{
// A vector is used here to ensure heap allocation without copy from stack.
// A vector is used here to ensure heap allocation without copy from stack
let mut f = vec![0u8; F];
let mut v = PublicBox::zero();
let p = path.as_ref();
@@ -357,20 +295,16 @@ impl<const N: usize> LoadValueB64 for PublicBox<N> {
}
impl<const N: usize> StoreValueB64 for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
self.inner.store_b64::<F, P>(path)
}
}
impl<const N: usize> StoreValueB64Writer for PublicBox<N> {
// No extra documentation here because the Trait already provides a good documentation.
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64_writer<const F: usize, W: std::io::Write>(
&self,
writer: W,
@@ -386,7 +320,6 @@ mod tests {
#[allow(clippy::module_inception)]
mod tests {
use crate::{Public, PublicBox};
use rand::Fill;
use rosenpass_util::{
b64::b64_encode,
file::{
@@ -516,112 +449,5 @@ mod tests {
fn test_public_box_load_store_base64() {
run_base64_load_store_test::<PublicBox<N>>();
}
/// Test the debug print function for [Public]
#[test]
fn test_debug_public() {
let p: Public<32> = Public::zero();
let _ = format!("{:?}", p);
}
/// Test that [Public] is correctly borrowed to a u8 array.
#[test]
fn test_borrow_public_sized() {
let p: Public<32> = Public::zero();
let borrowed: &[u8; 32] = &p;
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [Public] is correctly borrowed to a mutable u8 array.
#[test]
fn test_borrow_public_sized_mut() {
let mut p: Public<32> = Public::zero();
let borrowed: &mut [u8; 32] = &mut p;
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [Public] is correctly borrowed to a u8 slice.
#[test]
fn test_borrow_public_unsized() {
use std::borrow::Borrow;
let p: Public<32> = Public::zero();
let borrowed: &[u8] = p.borrow();
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [Public] is correctly borrowed to a mutable u8 slice.
#[test]
fn test_borrow_public_unsized_mut() {
use std::borrow::BorrowMut;
let mut p: Public<32> = Public::zero();
let borrowed: &mut [u8] = p.borrow_mut();
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [PublicBox] is correctly created from a slice.
#[test]
fn test_public_box_from_slice() {
let my_slice: &[u8; 32] = &[0; 32];
let p: PublicBox<32> = PublicBox::from_slice(my_slice);
assert_eq!(p.deref(), my_slice);
}
/// Test that [PublicBox] can correctly be created with its [PublicBox::new] function.
#[test]
fn test_public_box_new() {
let pb = PublicBox::new([42; 32]);
assert_eq!(pb.deref(), &[42; 32]);
}
/// Test the randomize functionality of [PublicBox].
#[test]
fn test_public_box_randomize() {
let mut pb: PublicBox<32> = PublicBox::zero();
pb.randomize();
pb.try_fill(&mut crate::rand::rng()).unwrap();
// Can't really assert anything here until we have can predict the randomness
// by derandomizing the RNG for tests.
}
/// Test the [Debug] print of [PublicBox]
#[test]
fn test_public_box_debug() {
let pb: PublicBox<32> = PublicBox::new([42; 32]);
let _ = format!("{:?}", pb);
}
/// Test that [PublicBox] is correctly borrowed to a u8 array.
#[test]
fn test_borrow_public_box_sized() {
let p: PublicBox<32> = PublicBox::zero();
let borrowed: &[u8; 32] = &p;
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [PublicBox] is correctly borrowed to a mutable u8 array.
#[test]
fn test_borrow_public_box_sized_mut() {
let mut p: PublicBox<32> = PublicBox::zero();
let borrowed: &mut [u8; 32] = &mut p;
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [PublicBox] is correctly borrowed to a u8 slice.
#[test]
fn test_borrow_public_box_unsized() {
use std::borrow::Borrow;
let p: PublicBox<32> = PublicBox::zero();
let borrowed: &[u8] = p.borrow();
assert_eq!(borrowed, &[0; 32]);
}
/// Test that [Public] is correctly borrowed to a mutable u8 slice.
#[test]
fn test_borrow_public_box_unsized_mut() {
use std::borrow::BorrowMut;
let mut p: PublicBox<32> = PublicBox::zero();
let borrowed: &mut [u8] = p.borrow_mut();
assert_eq!(borrowed, &[0; 32]);
}
}
}

View File

@@ -1,9 +1,5 @@
//! This module provides functionality for generating random numbers using the [rand] crate.
/// We use the [ThreadRng](rand::rngs::ThreadRng) for randomness in this crate.
pub type Rng = rand::rngs::ThreadRng;
/// Get the default [Rng].
pub fn rng() -> Rng {
rand::thread_rng()
}

View File

@@ -27,7 +27,6 @@ thread_local! {
static SECRET_CACHE: RefCell<SecretMemoryPool> = RefCell::new(SecretMemoryPool::new());
}
/// Executes the given function `f` with the [SECRET_CACHE] as a parameter.
fn with_secret_memory_pool<Fn, R>(mut f: Fn) -> R
where
Fn: FnMut(Option<&mut SecretMemoryPool>) -> R,
@@ -48,47 +47,37 @@ where
.unwrap_or_else(|| f(None))
}
/// Wrapper around SecretBox that applies automatic zeroization.
// Wrapper around SecretBox that applies automatic zeroization
#[derive(Debug)]
struct ZeroizingSecretBox<T: Zeroize + ?Sized>(Option<SecretBox<T>>);
impl<T: Zeroize> ZeroizingSecretBox<T> {
/// Creates a new [ZeroizingSecretBox] around `boxed` for the type `T`, where `T` must implement
/// [Zeroize].
fn new(boxed: T) -> Self {
ZeroizingSecretBox(Some(secret_box(boxed)))
}
}
impl<T: Zeroize + ?Sized> ZeroizingSecretBox<T> {
/// Creates a new [ZeroizingSecretBox] from a [SecretBox] for the type `T`,
/// which must implement [Zeroize] but does not have to be [Sized].
fn from_secret_box(inner: SecretBox<T>) -> Self {
Self(Some(inner))
}
/// Consumes the [ZeroizingSecretBox] and returns the content in a [SecretBox] for the type `T`,
/// which must implement [Zeroize] but does not have to be [Sized].
fn take(mut self) -> SecretBox<T> {
self.0.take().unwrap()
}
}
// Indicate that a Secret is always zeroized when it is dropped.
impl<T: Zeroize + ?Sized> ZeroizeOnDrop for ZeroizingSecretBox<T> {}
impl<T: Zeroize + ?Sized> Zeroize for ZeroizingSecretBox<T> {
fn zeroize(&mut self) {
if let Some(inner) = &mut self.0 {
let inner: &mut SecretBox<T> = inner; // Type annotation.
let inner: &mut SecretBox<T> = inner; // type annotation
inner.zeroize()
}
}
}
impl<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
/// Releases the memory of this [ZeroizingSecretBox]. In contrast to usual implementations
/// of [Drop], we zeroize the memory before dropping it. This fulfills the promises we make
/// by implementing [ZeroizeOnDrop].
fn drop(&mut self) {
self.zeroize()
}
@@ -97,32 +86,29 @@ impl<T: Zeroize + ?Sized> Drop for ZeroizingSecretBox<T> {
impl<T: Zeroize + ?Sized> Deref for ZeroizingSecretBox<T> {
type Target = T;
// No extra documentation here because the Trait already provides a good documentation.
fn deref(&self) -> &T {
self.0.as_ref().unwrap()
}
}
impl<T: Zeroize + ?Sized> DerefMut for ZeroizingSecretBox<T> {
// No extra documentation here because the Trait already provides a good documentation.
fn deref_mut(&mut self) -> &mut T {
self.0.as_mut().unwrap()
}
}
/// Pool that stores secret memory allocations.
/// Pool that stores secret memory allocations
///
/// Allocation of secret memory is expensive. Thus, this struct provides a
/// pool of secret memory, readily available to yield protected, slices of
/// memory.
#[derive(Debug)] // TODO check on Debug derive, is that clever
struct SecretMemoryPool {
/// A pool to reuse secret memory
pool: HashMap<usize, Vec<ZeroizingSecretBox<[u8]>>>,
}
impl SecretMemoryPool {
/// Create a new [SecretMemoryPool].
/// Create a new [SecretMemoryPool]
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
@@ -130,7 +116,7 @@ impl SecretMemoryPool {
}
}
/// Return secret back to the pool for future re-use.
/// Return secret back to the pool for future re-use
pub fn release<const N: usize>(&mut self, mut sec: ZeroizingSecretBox<[u8; N]>) {
sec.zeroize();
@@ -148,7 +134,7 @@ impl SecretMemoryPool {
/// Take protected memory from the pool, allocating new one if no suitable
/// chunk is found in the inventory.
///
/// The secret is guaranteed to be full of nullbytes.
/// The secret is guaranteed to be full of nullbytes
pub fn take<const N: usize>(&mut self) -> ZeroizingSecretBox<[u8; N]> {
let entry = self.pool.entry(N).or_default();
let inner = match entry.pop() {
@@ -159,50 +145,22 @@ impl SecretMemoryPool {
}
}
/// [Secret] stores its memory in a way that makes it difficult for threat actors to access it
/// without permission. This is achieved through the following mechanisms:
/// 1. Data in a [Secret] is stored in memory that is allocated and managed by memory allocators
/// that make it more difficult for threat actors to access the memory. Specifically, the
/// allocators from [memsec] are supported.
/// 2. Memory that is allocated for a [Secret] is zeroized before it is used for anything else.
///
/// In order to use a [Secret], we have to decide on the secure allocator to use beforehand
/// calling either
/// [secret_policy_use_only_malloc_secrets](crate::secret_policy_use_only_malloc_secrets),
/// [secret_policy_use_only_memfd_secrets](crate::secret_policy_use_only_memfd_secrets)
/// or [secret_policy_try_use_memfd_secrets](crate::secret_policy_try_use_memfd_secrets).
///
/// You can use a [Secret] as follows:
/// ```rust
/// # use zeroize::Zeroize;
/// # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
///
/// // We have to define the security policy before using Secrets.
/// secret_policy_use_only_malloc_secrets();
/// let mut my_secret: Secret<32> = Secret::zero();
/// // Fill with some random data that I can use a cryptographic key later on.
/// my_secret.randomize();
/// // In case I accidentally print my secret in a debug, it's still not leaked:
/// assert_eq!(format!("{:?}", my_secret), "<SECRET>");
/// // If you need to, you can zeroize a [Secret] at any time it's necessary:
/// my_secret.zeroize();
/// ```
/// Storage for secret data
pub struct Secret<const N: usize> {
storage: Option<ZeroizingSecretBox<[u8; N]>>,
}
impl<const N: usize> Secret<N> {
/// Create a new [Secret] from a byte-slice.
pub fn from_slice(slice: &[u8]) -> Self {
let mut new_self = Self::zero();
new_self.secret_mut().copy_from_slice(slice);
new_self
}
/// Returns a new [Secret] that is zero initialized.
/// Returns a new [Secret] that is zero initialized
pub fn zero() -> Self {
// Using [SecretMemoryPool] here because this operation is expensive,
// yet it is used in hot loops.
// yet it is used in hot loops
let buf = with_secret_memory_pool(|pool| {
pool.map(|p| p.take())
.unwrap_or_else(|| ZeroizingSecretBox::new([0u8; N]))
@@ -211,30 +169,28 @@ impl<const N: usize> Secret<N> {
Self { storage: Some(buf) }
}
/// Returns a new [Secret] that is randomized.
/// Returns a new [Secret] that is randomized
pub fn random() -> Self {
mutating(Self::zero(), |r| r.randomize())
}
/// Sets all data of an existing [Secret] to random bytes.
/// Sets all data an existing secret to random bytes
pub fn randomize(&mut self) {
self.try_fill(&mut crate::rand::rng()).unwrap()
}
/// Borrows the data.
/// Borrows the data
pub fn secret(&self) -> &[u8; N] {
self.storage.as_ref().unwrap()
}
/// Borrows the data mutably.
/// Borrows the data mutably
pub fn secret_mut(&mut self) -> &mut [u8; N] {
self.storage.as_mut().unwrap()
}
}
impl<const N: usize> Randomize for Secret<N> {
/// Tries to fill this [Secret] with random data. The [Secret] is first zeroized
/// to make sure that the barriers from the zeroize crate take effect.
fn try_fill<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Result<(), rand::Error> {
// Zeroize self first just to make sure the barriers from the zeroize create take
// effect to prevent the compiler from optimizing this away.
@@ -244,10 +200,8 @@ impl<const N: usize> Randomize for Secret<N> {
}
}
// Indicate that a [Secret] is always zeroized when it is dropped.
impl<const N: usize> ZeroizeOnDrop for Secret<N> {}
impl<const N: usize> Zeroize for Secret<N> {
// No extra documentation here because the Trait already provides a good documentation.
fn zeroize(&mut self) {
if let Some(inner) = &mut self.storage {
inner.zeroize()
@@ -255,14 +209,7 @@ impl<const N: usize> Zeroize for Secret<N> {
}
}
// Our implementation of Drop gives back the allocated secret memory to the secret memory pool.
impl<const N: usize> Drop for Secret<N> {
/// Release the memory of this [Secret]. In contrast to usual implementations to [Drop] we
/// do the following:
/// 1. The memory of this [Secret] gets zeroized as required by [ZeroizeOnDrop].
/// 2. The memory is returned to a memory pool of specially secure memory to be reused.
///
/// This behaviour fulfills the promises we make by implementing [ZeroizeOnDrop].
fn drop(&mut self) {
with_secret_memory_pool(|pool| {
if let Some((pool, secret)) = pool.zip(self.storage.take()) {
@@ -293,7 +240,6 @@ impl<const N: usize> fmt::Debug for Secret<N> {
impl<const N: usize> LoadValue for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut v = Self::random();
let p = path.as_ref();
@@ -307,7 +253,6 @@ impl<const N: usize> LoadValue for Secret<N> {
impl<const N: usize> LoadValueB64 for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn load_b64<const F: usize, P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
let mut f: Secret<F> = Secret::random();
let mut v = Self::random();
@@ -327,7 +272,6 @@ impl<const N: usize> LoadValueB64 for Secret<N> {
impl<const N: usize> StoreValueB64 for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64<const F: usize, P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
let p = path.as_ref();
@@ -347,7 +291,6 @@ impl<const N: usize> StoreValueB64 for Secret<N> {
impl<const N: usize> StoreValueB64Writer for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_b64_writer<const F: usize, W: Write>(&self, mut writer: W) -> anyhow::Result<()> {
let mut f: Secret<F> = Secret::random();
let encoded_str = b64_encode(self.secret(), f.secret_mut())
@@ -364,13 +307,11 @@ impl<const N: usize> StoreValueB64Writer for Secret<N> {
impl<const N: usize> StoreSecret for Secret<N> {
type Error = anyhow::Error;
// No extra documentation here because the Trait already provides a good documentation.
fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
fopen_w(path, Visibility::Secret)?.write_all(self.secret())?;
Ok(())
}
// No extra documentation here because the Trait already provides a good documentation.
fn store<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
fopen_w(path, Visibility::Public)?.write_all(self.secret())?;
Ok(())
@@ -379,10 +320,7 @@ impl<const N: usize> StoreSecret for Secret<N> {
#[cfg(test)]
mod test {
use crate::{
secret_policy_try_use_memfd_secrets, secret_policy_use_only_malloc_secrets,
test_spawn_process_provided_policies,
};
use crate::test_spawn_process_provided_policies;
use super::*;
use std::{fs, os::unix::fs::PermissionsExt};
@@ -390,7 +328,7 @@ mod test {
procspawn::enable_test_support!();
/// Check that we can alloc using the magic pool.
/// check that we can alloc using the magic pool
#[test]
fn secret_memory_pool_take() {
test_spawn_process_provided_policies!({
@@ -401,7 +339,7 @@ mod test {
});
}
/// Check that a secret lives, even if its [SecretMemoryPool] is deleted.
/// check that a secret lives, even if its [SecretMemoryPool] is deleted
#[test]
fn secret_memory_pool_drop() {
test_spawn_process_provided_policies!({
@@ -413,7 +351,7 @@ mod test {
});
}
/// Check that a secret can be reborn, freshly initialized with zero.
/// check that a secret can be reborn, freshly initialized with zero
#[test]
fn secret_memory_pool_release() {
test_spawn_process_provided_policies!({
@@ -434,7 +372,7 @@ mod test {
});
}
/// Test loading a secret from an example file, and then storing it again in a different file.
/// test loading a secret from an example file, and then storing it again in a different file
#[test]
fn test_secret_load_store() {
test_spawn_process_provided_policies!({
@@ -471,8 +409,7 @@ mod test {
});
}
/// Test loading a base64 encoded secret from an example file, and then storing it again in
/// a different file.
/// test loading a base64 encoded secret from an example file, and then storing it again in a different file
#[test]
fn test_secret_load_store_base64() {
test_spawn_process_provided_policies!({
@@ -526,33 +463,4 @@ mod test {
assert_eq!(metadata.permissions().mode() & 0o000777, 0o600);
});
}
/// Test the creation of a [ZeroizingSecretBox] using its [new](ZeroizingSecretBox::new)
/// function.
#[test]
fn test_zeroizing_secret_box_new() {
struct DummyZeroizing {
inner_dummy: [u8; 32],
}
impl Zeroize for DummyZeroizing {
fn zeroize(&mut self) {
self.inner_dummy = [0; 32];
}
}
let dummy = DummyZeroizing {
inner_dummy: [42; 32],
};
secret_policy_use_only_malloc_secrets();
let mut zsb: ZeroizingSecretBox<DummyZeroizing> = ZeroizingSecretBox::new(dummy);
zsb.zeroize();
assert_eq!(zsb.inner_dummy, [0; 32]);
}
/// Test the debug print of [Secret].
#[test]
fn test_debug_secret() {
secret_policy_use_only_malloc_secrets();
let my_secret: Secret<32> = Secret::zero();
assert_eq!(format!("{:?}", my_secret), "<SECRET>");
}
}

View File

@@ -1,12 +1,3 @@
//! Utilities for encoding length-prefixed messages that can be transmitted via I/O streams.
//!
//! Messages are prefixed with an unsigned 64-bit little-endian length header, followed by the
//! message payload. Each [`LengthPrefixEncoder`] maintains internal buffers and additional state for as-yet
//! incomplete messages.
//!
//! It also performs sanity checks and handles typical error conditions that may be encountered
//! when writing structured data to an output sink (such as stdout or an active socket connection).
use std::{
borrow::{Borrow, BorrowMut},
cmp::min,
@@ -18,32 +9,31 @@ use zeroize::Zeroize;
use crate::{io::IoResultKindHintExt, result::ensure_or};
/// Size in bytes of the message header carrying length information.
/// Currently, HEADER_SIZE is always 8 bytes and encodes a 64-bit little-endian number.
/// Size of the length prefix header in bytes - equal to the size of a u64
pub const HEADER_SIZE: usize = std::mem::size_of::<u64>();
#[derive(Error, Debug, Clone, Copy)]
#[error("Write position is out of buffer bounds")]
/// Error indicating that the given offset exceeds the bounds of the allocated buffer.
/// Error type indicating that a write position is beyond the boundaries of the allocated buffer
pub struct PositionOutOfBufferBounds;
#[derive(Error, Debug, Clone, Copy)]
#[error("Write position is out of message bounds")]
/// Error indicating that the given offset exceeds the bounds of the message.
/// Error type indicating that a write position is beyond the boundaries of the message
pub struct PositionOutOfMessageBounds;
#[derive(Error, Debug, Clone, Copy)]
#[error("Write position is out of header bounds")]
/// Error indicating that the given offset exceeds the bounds of the header.
/// Error type indicating that a write position is beyond the boundaries of the header
pub struct PositionOutOfHeaderBounds;
#[derive(Error, Debug, Clone, Copy)]
#[error("Message length is bigger than buffer length")]
/// Error indicating that the message size is larger than the available buffer space.
/// Error type indicating that the message length is larger than the available buffer space
pub struct MessageTooLarge;
#[derive(Error, Debug, Clone, Copy)]
/// Error enum representing sanity check failures related to the message size.
/// Error type for message length sanity checks
pub enum MessageLenSanityError {
/// Error indicating position is beyond message boundaries
#[error("{0:?}")]
@@ -54,7 +44,7 @@ pub enum MessageLenSanityError {
}
#[derive(Error, Debug, Clone, Copy)]
/// Error enum representing sanity check failures related to out-of-bounds memory access.
/// Error type for position bounds checking
pub enum PositionSanityError {
/// Error indicating position is beyond message boundaries
#[error("{0:?}")]
@@ -65,7 +55,7 @@ pub enum PositionSanityError {
}
#[derive(Error, Debug, Clone, Copy)]
/// Error enum representing sanity check failures of any kind.
/// Error type combining all sanity check errors
pub enum SanityError {
/// Error indicating position is beyond message boundaries
#[error("{0:?}")]
@@ -111,7 +101,7 @@ impl From<PositionSanityError> for SanityError {
}
}
/// Return type for `WriteToIo` operations, containing the number of bytes written and a completion flag.
/// Result of a write operation on an IO stream
pub struct WriteToIoReturn {
/// Number of bytes successfully written in this operation
pub bytes_written: usize,
@@ -120,55 +110,7 @@ pub struct WriteToIoReturn {
}
#[derive(Clone, Copy, Debug)]
/// An encoder for length-prefixed messages.
///
/// # Examples
///
/// ## Writing to output streams
///
/// Simplified usage example:
///
/// ```rust
/// use rosenpass_util::length_prefix_encoding::encoder::LengthPrefixEncoder;
/// use rosenpass_util::length_prefix_encoding::encoder::HEADER_SIZE;
///
/// let message = String::from("hello world");
/// let mut encoder = LengthPrefixEncoder::from_message(message.as_bytes());
///
/// let mut output = Vec::new();
/// encoder.write_all_to_stdio(&mut output).expect("failed to write_all");
///
/// assert_eq!(output.len(), message.len() + HEADER_SIZE);
///
/// let (header, body) = output.split_at(HEADER_SIZE);
/// let length = u64::from_le_bytes(header.try_into().unwrap());
///
/// assert_eq!(length as usize, message.len());
/// assert_eq!(body, message.as_bytes());
/// ```
///
/// For more examples, see also:
///
/// * [Self::write_all_to_stdio]
/// * [Self::write_to_stdio]
///
///
/// ## Basic error handling
///
/// Creating an encoder with invalid parameters triggers one of the various sanity checks:
///
/// ```rust
/// use rosenpass_util::length_prefix_encoding::encoder::{LengthPrefixEncoder, MessageLenSanityError};
///
/// let message_size = 32;
/// let message = vec![0u8; message_size];
///
/// // The sanity check prevents an unsafe out-of-bounds access here
/// let err = LengthPrefixEncoder::from_short_message(message, 2 * message_size)
/// .expect_err("OOB access should fail");
/// assert!(matches!(err, MessageLenSanityError::MessageTooLarge(_)));
/// ```
/// Length-prefixed encoder that adds a length header to data before writing
pub struct LengthPrefixEncoder<Buf: Borrow<[u8]>> {
buf: Buf,
header: [u8; HEADER_SIZE],
@@ -193,10 +135,6 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
}
/// Creates a new encoder using part of the buffer as a message
///
/// # Example
///
/// See [Basic error handling](#basic-error-handling)
pub fn from_short_message(msg: Buf, len: usize) -> Result<Self, MessageLenSanityError> {
let mut r = Self::from_message(msg);
r.set_message_len(len)?;
@@ -211,39 +149,12 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
}
/// Consumes the encoder and returns the underlying buffer
///
/// # Example
///
/// ```rust
/// use rosenpass_util::length_prefix_encoding::encoder::LengthPrefixEncoder;
///
/// let msg = String::from("hello world");
/// let encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
/// let msg_buffer = encoder.into_buffer();
/// assert_eq!(msg_buffer, msg.as_bytes());
/// ```
pub fn into_buffer(self) -> Buf {
let Self { buf, .. } = self;
buf
}
/// Consumes the encoder and returns buffer, message length and write position
///
/// # Example
///
/// ```rust
/// use rosenpass_util::length_prefix_encoding::encoder::LengthPrefixEncoder;
///
/// let msg = String::from("hello world");
/// let encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
/// assert!(encoder.encoded_message_bytes() > msg.len());
/// assert!(!encoder.exhausted());
///
/// let (msg_buffer, msg_length, write_offset) = encoder.into_parts();
/// assert_eq!(msg_buffer, msg.as_bytes());
/// assert_eq!(write_offset, 0);
/// assert_eq!(msg_length, msg.len());
/// ```
pub fn into_parts(self) -> (Buf, usize, usize) {
let len = self.message_len();
let pos = self.writing_position();
@@ -258,25 +169,6 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
}
/// Writes the full message to an IO writer, retrying on interrupts
///
/// # Example
///
/// ```rust
/// # use std::io::Cursor;
/// # use rosenpass_util::length_prefix_encoding::encoder::{LengthPrefixEncoder, HEADER_SIZE};
/// let msg = String::from("message in a bottle");
/// let prefixed_msg_size = msg.len() + HEADER_SIZE;
///
/// let mut encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
///
/// // Fast-forward - behaves as if the HEADER had already been written; only the message remains
/// encoder
/// .set_header_offset(HEADER_SIZE)
/// .expect("failed to move cursor");
/// let mut sink = Cursor::new(vec![0; prefixed_msg_size + 1]);
/// encoder.write_all_to_stdio(&mut sink).expect("write failed");
/// assert_eq!(&sink.get_ref()[0..msg.len()], msg.as_bytes());
/// ```
pub fn write_all_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<()> {
use io::ErrorKind as K;
loop {
@@ -293,45 +185,7 @@ impl<Buf: Borrow<[u8]>> LengthPrefixEncoder<Buf> {
}
}
/// Attempts to write the next chunk of data to an IO writer, returning the number of bytes written and completion flag
///
/// # Example
///
/// ```rust
/// # use std::io::Cursor;
/// # use rosenpass_util::length_prefix_encoding::encoder::{LengthPrefixEncoder, WriteToIoReturn, HEADER_SIZE};
/// let msg = String::from("Hello world");
/// let prefixed_msg_size = msg.len() + HEADER_SIZE;
///
/// let mut encoder = LengthPrefixEncoder::from_parts(msg.as_bytes(), msg.len(), 0).unwrap();
/// assert_eq!(encoder.encoded_message_bytes(), prefixed_msg_size);
/// assert!(!encoder.exhausted());
///
/// let mut dummy_stdout = Cursor::new(vec![0; prefixed_msg_size + 1]);
///
/// loop {
/// let result: WriteToIoReturn = encoder
/// .write_to_stdio(&mut dummy_stdout)
/// .expect("write failed");
/// if dummy_stdout.position() as usize >= prefixed_msg_size {
/// // The entire message should've been written (and the encoder state reflect this)
/// assert!(result.done);
/// assert_eq!(result.bytes_written, msg.len());
/// assert_eq!(encoder.header_written(), (msg.len() as u64).to_le_bytes());
/// assert_eq!(encoder.message_written(), msg.as_bytes());
/// break;
/// }
/// }
/// let buffer_bytes = dummy_stdout.get_ref();
/// match String::from_utf8(buffer_bytes.to_vec()) {
/// Ok(buffer_str) => assert_eq!(&buffer_str[HEADER_SIZE..prefixed_msg_size], msg),
/// Err(err) => println!("Error converting buffer to String: {:?}", err),
/// }
/// assert_eq!(
/// &dummy_stdout.get_ref()[HEADER_SIZE..prefixed_msg_size],
/// msg.as_bytes()
/// );
/// ```
/// Writes the next chunk of data to an IO writer and returns number of bytes written and completion status
pub fn write_to_stdio<W: io::Write>(&mut self, mut w: W) -> io::Result<WriteToIoReturn> {
if self.exhausted() {
return Ok(WriteToIoReturn {
@@ -584,150 +438,3 @@ impl<Buf: BorrowMut<[u8]>> Zeroize for LengthPrefixEncoder<Buf> {
self.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lpe_error_conversion_upcast_valid() {
let len_error = MessageTooLarge;
let len_sanity_error: MessageLenSanityError = len_error.into();
let sanity_error: SanityError = len_error.into();
assert!(matches!(sanity_error, SanityError::MessageTooLarge(_)));
let sanity_error: SanityError = len_sanity_error.into();
assert!(matches!(sanity_error, SanityError::MessageTooLarge(_)));
let pos_error = PositionOutOfBufferBounds;
let pos_sanity_error: PositionSanityError = pos_error.into();
let sanity_error: SanityError = pos_error.into();
assert!(matches!(
sanity_error,
SanityError::PositionOutOfBufferBounds(_)
));
let sanity_error: SanityError = pos_sanity_error.into();
assert!(matches!(
sanity_error,
SanityError::PositionOutOfBufferBounds(_)
));
}
#[test]
fn test_lpe_error_conversion_downcast_invalid() {
let pos_error = PositionOutOfBufferBounds;
let sanity_error = SanityError::PositionOutOfBufferBounds(pos_error.into());
match MessageLenSanityError::try_from(sanity_error) {
Ok(_) => panic!("Conversion should always fail (incompatible enum variant)"),
Err(err) => assert!(matches!(err, PositionOutOfBufferBounds)),
}
}
#[test]
fn test_write_to_stdio_cursor() {
use std::io::Cursor;
let msg = String::from("Hello world");
let prefixed_msg_size = msg.len() + HEADER_SIZE;
let mut encoder = LengthPrefixEncoder::from_parts(msg.as_bytes(), msg.len(), 0).unwrap();
assert_eq!(encoder.encoded_message_bytes(), prefixed_msg_size);
assert!(!encoder.exhausted());
let mut dummy_stdout = Cursor::new(vec![0; prefixed_msg_size + 1]);
loop {
let result: WriteToIoReturn = encoder
.write_to_stdio(&mut dummy_stdout)
.expect("write failed");
if dummy_stdout.position() as usize >= prefixed_msg_size {
// The entire message should've been written (and the encoder state reflect this)
assert!(result.done);
assert_eq!(result.bytes_written, msg.len());
assert_eq!(encoder.header_written(), (msg.len() as u64).to_le_bytes());
assert_eq!(encoder.message_written(), msg.as_bytes());
break;
}
}
let buffer_bytes = dummy_stdout.get_ref();
match String::from_utf8(buffer_bytes.to_vec()) {
Ok(buffer_str) => assert_eq!(&buffer_str[HEADER_SIZE..prefixed_msg_size], msg),
Err(err) => println!("Error converting buffer to String: {:?}", err),
}
assert_eq!(
&dummy_stdout.get_ref()[HEADER_SIZE..prefixed_msg_size],
msg.as_bytes()
);
}
#[test]
fn test_write_offset_header() {
use std::io::Cursor;
let mut msg = Vec::<u8>::new();
msg.extend_from_slice(b"cats");
msg.extend_from_slice(b" and dogs");
let msg_len = msg.len();
let prefixed_msg_size = msg_len + HEADER_SIZE;
msg.extend_from_slice(b" and other animals"); // To be discarded
let mut encoder = LengthPrefixEncoder::from_short_message(msg.clone(), msg_len).unwrap();
// Only the short message should have been stored (and the unused part discarded)
assert_eq!(encoder.message_mut(), b"cats and dogs");
assert_eq!(encoder.message_written_mut(), []);
assert_eq!(encoder.message_left_mut(), b"cats and dogs");
assert_eq!(encoder.buf_mut(), &msg);
// Fast-forward as if the header had already been sent - only the message remains
encoder
.set_header_offset(HEADER_SIZE)
.expect("failed to move cursor");
let mut sink = Cursor::new(vec![0; prefixed_msg_size + 1]);
encoder.write_all_to_stdio(&mut sink).expect("write failed");
assert_eq!(&sink.get_ref()[0..msg_len], &msg[0..msg_len]);
assert_eq!(encoder.message_mut(), b"cats and dogs");
assert_eq!(encoder.message_written_mut(), b"cats and dogs");
assert_eq!(encoder.message_left_mut(), []);
assert_eq!(encoder.buf_mut(), &msg);
}
#[test]
fn test_some_assembly_required() {
let msg = String::from("hello world");
let encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
assert!(encoder.encoded_message_bytes() > msg.len());
assert!(!encoder.exhausted());
let (msg_buffer, msg_length, write_offset) = encoder.into_parts();
assert_eq!(msg_buffer, msg.as_bytes());
assert_eq!(write_offset, 0);
assert_eq!(msg_length, msg.len());
}
#[test]
fn test_restart_write_reset() {
let msg = String::from("hello world");
let mut encoder = LengthPrefixEncoder::from_message(msg.as_bytes());
assert_eq!(encoder.writing_position(), 0);
encoder.set_writing_position(4).unwrap();
assert_eq!(encoder.writing_position(), 4);
encoder.restart_write();
assert_eq!(encoder.writing_position(), 0);
}
#[test]
fn test_zeroize_state() {
use zeroize::Zeroize;
let mut msg = Vec::<u8>::new();
msg.extend_from_slice(b"test");
let mut encoder = LengthPrefixEncoder::from_message(msg.clone());
assert_eq!(encoder.message(), msg);
encoder.zeroize();
assert_eq!(encoder.message(), []);
}
}

View File

@@ -1,2 +1,4 @@
/// Module that handles decoding functionality
pub mod decoder;
/// Module that handles encoding functionality
pub mod encoder;

View File

@@ -11,43 +11,6 @@ use uds::UnixStreamExt as FdPassingExt;
use crate::{repeat, return_if};
/// A structure that facilitates writing data and file descriptors to a Unix domain socket
///
/// # Example
///
/// ```rust
/// use std::io::{Read, Write};
/// use std::net::UdpSocket;
/// use std::os::fd::{AsFd, AsRawFd};
///
/// use mio::net::UnixStream;
/// use rosenpass_util::mio::WriteWithFileDescriptors;
///
/// // Create socket descriptors that should be sent (not limited to UDP sockets)
/// let peer_endpoint = "[::1]:0";
/// let peer_socket = UdpSocket::bind(peer_endpoint).expect("bind failed");
/// let peer_socket_fd = peer_socket.as_fd();
/// let mut fds_to_send = vec![&peer_socket_fd].into();
///
/// // Create writable end (must be an Unix Domain Socket)
/// // In this case, the readable end of the connection can be ignored
/// let (mut dummy_sink, io_stream) = UnixStream::pair().expect("failed to create socket pair");
/// let mut writable_stream = WriteWithFileDescriptors::<UnixStream, _, _, _>::new(
/// &io_stream, &mut fds_to_send);
///
/// // Send data and file descriptors (note that at least one byte should be written)
/// writable_stream.write(&[0xffu8; 42]).expect("failed to write");
/// // Discard data; the dummy_sink is only required to keep the connection alive here
/// let mut recv_buffer = Vec::<u8>::new();
/// dummy_sink.read(&mut recv_buffer[..]).expect("error reading from socket");
/// writable_stream.flush().expect("failed to flush"); // Currently a NOOP
///
/// // The wrapped components can still be accessed
/// let (socket, fds) = writable_stream.into_parts();
/// assert_eq!(socket.as_raw_fd(), io_stream.as_raw_fd());
/// assert!(fds_to_send.is_empty(), "Failed to send file descriptors");
///
/// // Shutdown, cleanup, etc. goes here ...
/// ```
pub struct WriteWithFileDescriptors<Sock, Fd, BorrowSock, BorrowFds>
where
Sock: FdPassingExt,

View File

@@ -1,41 +1,3 @@
//! Client implementation for the WireGuard broker protocol.
//!
//! This module provides a client implementation that communicates with a WireGuard broker server
//! using a binary protocol. The client handles serialization and deserialization of messages,
//! error handling, and the core interaction flow.
//!
//! # Examples
//!
//! ```
//! use rosenpass_wireguard_broker::api::client::{BrokerClient, BrokerClientIo};
//! #[derive(Debug)]
//! struct MyIo;
//!
//! impl BrokerClientIo for MyIo {
//! type SendError = std::io::Error;
//! type RecvError = std::io::Error;
//!
//! fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError> {
//! // Implement sending logic
//! Ok(())
//! }
//!
//! fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError> {
//! // Implement receiving logic
//! Ok(None)
//! }
//! }
//!
//! // Create client with custom IO implementation
//! let mut client = BrokerClient::new(MyIo);
//! assert!(client.poll_response().unwrap().is_none());
//! ```
//!
//! # Protocol
//!
//! The client implements a simple request-response protocol for setting WireGuard pre-shared keys.
//! Messages are serialized using a binary format defined in the [`crate::api::msgs`] module.
use std::{borrow::BorrowMut, fmt::Debug};
use crate::{
@@ -51,13 +13,10 @@ use super::{
msgs::{Envelope, SetPskResponse},
};
/// Error type for polling responses from the broker server.
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerClientPollResponseError<RecvError> {
/// An IO error occurred while receiving the response
#[error(transparent)]
IoError(RecvError),
/// The received message was invalid or malformed
#[error("Invalid message.")]
InvalidMessage,
}
@@ -69,52 +28,34 @@ impl<RecvError> From<msgs::InvalidMessageTypeError> for BrokerClientPollResponse
}
}
/// Helper function that wraps a receive error into a `BrokerClientPollResponseError::IoError`
fn io_poller<RecvError>(e: RecvError) -> BrokerClientPollResponseError<RecvError> {
BrokerClientPollResponseError::<RecvError>::IoError(e)
}
/// Helper function that returns a `BrokerClientPollResponseError::InvalidMessage` error
fn invalid_msg_poller<RecvError>() -> BrokerClientPollResponseError<RecvError> {
BrokerClientPollResponseError::<RecvError>::InvalidMessage
}
/// Error type for setting pre-shared keys through the broker client.
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerClientSetPskError<SendError> {
/// Error encoding or decoding the message
#[error("Error with encoding/decoding message")]
MsgError,
/// Error in the broker configuration
#[error("Network Broker Config error: {0}")]
BrokerError(NetworkBrokerConfigErr),
/// IO error while sending the request
#[error(transparent)]
IoError(SendError),
/// Interface name exceeds maximum length
#[error("Interface name out of bounds")]
IfaceOutOfBounds,
}
/// Trait defining the IO operations required by the broker client.
///
/// Implementors must provide methods for sending and receiving binary messages.
pub trait BrokerClientIo {
/// Error type returned by send operations
type SendError;
/// Error type returned by receive operations
type RecvError;
/// Send a binary message
fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError>;
/// Receive a binary message, returning None if no message is available
fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError>;
}
/// Client for interacting with a WireGuard broker server.
///
/// The client handles the protocol-level communication with the server,
/// including message serialization and response handling.
#[derive(Debug)]
pub struct BrokerClient<Io>
where
@@ -127,37 +68,18 @@ impl<Io> BrokerClient<Io>
where
Io: BrokerClientIo + Debug,
{
/// Creates a new `BrokerClient` with the given IO implementation.
pub fn new(io: Io) -> Self {
Self { io }
}
/// Returns a reference to the underlying IO implementation.
pub fn io(&self) -> &Io {
&self.io
}
/// Returns a mutable reference to the underlying IO implementation.
pub fn io_mut(&mut self) -> &mut Io {
&mut self.io
}
/// Polls for a response from the broker server.
///
/// This method attempts to receive and parse a SetPsk response message from the server.
/// If no message is available, returns `Ok(None)`. If a message is received, it is
/// parsed and validated before being returned as `Ok(Some(result))`.
///
/// # Returns
/// - `Ok(Some(result))` if a valid response was received
/// - `Ok(None)` if no message was available
/// - `Err(BrokerClientPollResponseError)` if an error occurred during receiving or parsing
///
/// # Errors
/// Returns an error if:
/// - An IO error occurs while receiving the message
/// - The received message is invalid or malformed
/// - The message type is incorrect
pub fn poll_response(
&mut self,
) -> Result<Option<msgs::SetPskResult>, BrokerClientPollResponseError<Io::RecvError>> {
@@ -225,112 +147,3 @@ where
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use msgs::{MsgType, SetPskError, SetPskResponseReturnCode};
// Mock IO implementation for testing
#[derive(Debug)]
struct MockIo {
recv_data: Vec<u8>,
}
impl MockIo {
fn new() -> Self {
Self {
recv_data: Vec::new(),
}
}
fn set_recv_data(&mut self, data: Option<Vec<u8>>) {
self.recv_data = data.unwrap_or_default();
}
}
impl BrokerClientIo for MockIo {
type SendError = std::io::Error;
type RecvError = std::io::Error;
fn send_msg(&mut self, _buf: &[u8]) -> Result<(), Self::SendError> {
Ok(())
}
fn recv_msg(&mut self) -> Result<Option<&[u8]>, Self::RecvError> {
if self.recv_data.is_empty() {
Ok(None)
} else {
Ok(Some(&self.recv_data))
}
}
}
fn create_response_msg(return_code: u8) -> Vec<u8> {
let mut msg = vec![
MsgType::SetPsk as u8, // msg_type
0,
0,
0, // reserved bytes
];
msg.push(return_code); // return_code
msg
}
#[test]
fn test_poll_response_no_message() {
let io = MockIo::new();
let mut client = BrokerClient::new(io);
assert_eq!(client.poll_response().unwrap(), None);
}
#[test]
fn test_poll_response_success() {
let mut io = MockIo::new();
io.set_recv_data(Some(create_response_msg(
SetPskResponseReturnCode::Success as u8,
)));
let mut client = BrokerClient::new(io);
assert_eq!(client.poll_response().unwrap(), Some(Ok(())));
}
#[test]
fn test_poll_response_no_such_peer() {
let mut io = MockIo::new();
io.set_recv_data(Some(create_response_msg(
SetPskResponseReturnCode::NoSuchPeer as u8,
)));
let mut client = BrokerClient::new(io);
assert_eq!(
client.poll_response().unwrap(),
Some(Err(SetPskError::NoSuchPeer))
);
}
#[test]
fn test_poll_response_invalid_message_type() {
let mut io = MockIo::new();
io.set_recv_data(Some(vec![0xFF, 0, 0, 0, 0])); // Invalid message type
let mut client = BrokerClient::new(io);
assert!(matches!(
client.poll_response(),
Err(BrokerClientPollResponseError::InvalidMessage)
));
}
#[test]
fn test_poll_response_invalid_return_code() {
let mut io = MockIo::new();
io.set_recv_data(Some(create_response_msg(0xFF))); // Invalid return code
let mut client = BrokerClient::new(io);
assert!(matches!(
client.poll_response(),
Err(BrokerClientPollResponseError::InvalidMessage)
));
}
}

View File

@@ -1,7 +1,3 @@
//! This module provides [NetworkBrokerConfig] for configuring a
//! [BrokerServer](crate::api::server::BrokerServer) and tooling to serialize and deserialize these
//! configurations.
use crate::{SerializedBrokerConfig, WG_KEY_LEN, WG_PEER_LEN};
use derive_builder::Builder;
use rosenpass_secret_memory::{Public, Secret};
@@ -9,19 +5,13 @@ use rosenpass_secret_memory::{Public, Secret};
#[derive(Builder, Debug)]
#[builder(pattern = "mutable")]
//TODO: Use generics for iface, add additional params
/// Specifies a configuration for a [BrokerServer](crate::api::server::BrokerServer).
pub struct NetworkBrokerConfig<'a> {
/// The interface for the [BrokerServer](crate::api::server::BrokerServer).
pub iface: &'a str,
/// The peer identifier for the [BrokerServer](crate::api::server::BrokerServer).
pub peer_id: &'a Public<WG_PEER_LEN>,
/// The pre-shared key for the [BrokerServer](crate::api::server::BrokerServer) and the
/// interface.
pub psk: &'a Secret<WG_KEY_LEN>,
}
impl<'a> From<NetworkBrokerConfig<'a>> for SerializedBrokerConfig<'a> {
/// Transforms a [NetworkBrokerConfig] into a [SerializedBrokerConfig] meant for serialization.
fn from(src: NetworkBrokerConfig<'a>) -> SerializedBrokerConfig<'a> {
Self {
interface: src.iface.as_bytes(),
@@ -32,23 +22,15 @@ impl<'a> From<NetworkBrokerConfig<'a>> for SerializedBrokerConfig<'a> {
}
}
/// Error variants that can occur when loading a [NetworkBrokerConfig] from a
/// [SerializedBrokerConfig].
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum NetworkBrokerConfigErr {
/// Error indicating that the interface specification could not be read correctly.
#[error("Interface")]
Interface, // TODO, give this a better name.
Interface,
}
impl<'a> TryFrom<SerializedBrokerConfig<'a>> for NetworkBrokerConfig<'a> {
type Error = NetworkBrokerConfigErr;
/// Tries to load a [NetworkBrokerConfig] from a [SerializedBrokerConfig].
///
/// # Errors
/// Returns a [NetworkBrokerConfigErr::Interface]-error when the interface description
/// can not be parsed correctly.
fn try_from(value: SerializedBrokerConfig<'a>) -> Result<Self, Self::Error> {
let iface =
std::str::from_utf8(value.interface).map_err(|_| NetworkBrokerConfigErr::Interface)?;

View File

@@ -1,9 +1,3 @@
//! This module implements the binary WireGuard broker protocol in the form of the [client::BrokerClient]
//! and the [server::BrokerServer].
//!
//! Specifically, The protocol enables the client to tell the server to set a pre-shared key for a
//! wireguard interface.
pub mod client;
pub mod config;
pub mod msgs;

View File

@@ -1,19 +1,11 @@
//! This module defines message formats for messages in the Wireguard Broker protocol as well as
//! helper structures like errors and conversion functions.
use std::str::{from_utf8, Utf8Error};
use zerocopy::{AsBytes, FromBytes, FromZeroes};
/// The number of bytes reserved for overhead when packaging data.
pub const ENVELOPE_OVERHEAD: usize = 1 + 3;
/// The buffer size for request messages.
pub const REQUEST_MSG_BUFFER_SIZE: usize = ENVELOPE_OVERHEAD + 32 + 32 + 1 + 255;
/// The buffer size for responses.
pub const RESPONSE_MSG_BUFFER_SIZE: usize = ENVELOPE_OVERHEAD + 1;
/// Envelope for messages being passed around.
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct Envelope<M: AsBytes + FromBytes> {
@@ -25,43 +17,25 @@ pub struct Envelope<M: AsBytes + FromBytes> {
pub payload: M,
}
/// Message format for requests to set a pre-shared key.
/// # Example
///
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct SetPskRequest {
/// The pre-shared key.
pub psk: [u8; 32],
/// The identifier of the peer.
pub peer_id: [u8; 32],
/// The size for the interface
pub iface_size: u8, // TODO: We should have variable length strings in lenses
/// The buffer for the interface.
pub iface_buf: [u8; 255],
}
impl SetPskRequest {
/// Gets the interface specification as byte slice.
pub fn iface_bin(&self) -> &[u8] {
let len = self.iface_size as usize;
&self.iface_buf[..len]
}
/// Gets the interface specification as a `&str`.
///
/// # Errors
/// Returns a [Utf8Error] if the interface specification isn't utf8 encoded.
pub fn iface(&self) -> Result<&str, Utf8Error> {
from_utf8(self.iface_bin())
}
/// Sets the interface specification to `iface`. No check is made whether `iface` is correctly
/// encoded as utf8.
///
/// # Result
/// Returns [None] if `iface` is longer than 255 bytes. Otherwise, it returns
/// [Some(())](Some).
pub fn set_iface_bin(&mut self, iface: &[u8]) -> Option<()> {
(iface.len() < 256).then_some(())?; // Assert iface.len() < 256
@@ -73,24 +47,17 @@ impl SetPskRequest {
Some(())
}
/// Sets the interface specification to `iface`.
///
/// # Result
/// Returns [None] if `iface` is longer than 255 bytes. Otherwise, it returns
/// [Some(())](Some).
pub fn set_iface(&mut self, iface: &str) -> Option<()> {
self.set_iface_bin(iface.as_bytes())
}
}
/// Message format for response to the set pre-shared key operation.
#[repr(packed)]
#[derive(AsBytes, FromBytes, FromZeroes)]
pub struct SetPskResponse {
pub return_code: u8,
}
/// Error type for the errors that can occur when setting a pre-shared key.
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum SetPskError {
#[error("The wireguard pre-shared-key assignment broker experienced an internal error.")]
@@ -103,12 +70,6 @@ pub enum SetPskError {
pub type SetPskResult = Result<(), SetPskError>;
/// The return codes and their meanings for the set psk response operation.
///
/// [SetPskResponseReturnCode] is represented by by a single `u8` as required by the protocol.
///
/// # Example
/// See [SetPskResponseReturnCode::try_from] for an example.
#[repr(u8)]
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum SetPskResponseReturnCode {
@@ -124,19 +85,6 @@ pub struct InvalidSetPskResponseError;
impl TryFrom<u8> for SetPskResponseReturnCode {
type Error = InvalidSetPskResponseError;
/// Parse a [u8] as a [MsgType].
///
/// # Example
/// ```
/// # use rosenpass_wireguard_broker::api::msgs::{InvalidSetPskResponseError, SetPskResponseReturnCode};
/// let return_code: u8 = 0x00; // Usually specifically set or comes out of a message.
/// let res = SetPskResponseReturnCode::try_from(return_code);
/// assert!(res.is_ok());
/// assert_eq!(res.unwrap(), SetPskResponseReturnCode::Success);
/// # Ok::<(), InvalidSetPskResponseError>(())
/// ```
/// # Errors
/// Returns a [InvalidSetPskResponseError] if `value` does not correspond to a known return code.
fn try_from(value: u8) -> Result<Self, Self::Error> {
use SetPskResponseReturnCode::*;
match value {
@@ -150,9 +98,6 @@ impl TryFrom<u8> for SetPskResponseReturnCode {
}
impl From<SetPskResponseReturnCode> for SetPskResult {
/// A [SetPskResult] can directly be deduced from a [SetPskResponseReturnCode].
/// An [Ok] type is only returned if `value` is [SetPskResponseReturnCode::Success].
/// Otherwise, an appropriate variant of [SetPskError] will be returned.
fn from(value: SetPskResponseReturnCode) -> Self {
use SetPskError as E;
use SetPskResponseReturnCode as C;
@@ -166,7 +111,6 @@ impl From<SetPskResponseReturnCode> for SetPskResult {
}
impl From<SetPskResult> for SetPskResponseReturnCode {
/// A [SetPskResponseReturnCode] can directly be deduced from a [SetPskResult].
fn from(value: SetPskResult) -> Self {
use SetPskError as E;
use SetPskResponseReturnCode as C;
@@ -179,45 +123,18 @@ impl From<SetPskResult> for SetPskResponseReturnCode {
}
}
/// The types of messages supported by this crate. At the time of writing, this is only
/// the message to set a pre-shared key.
///
/// [MsgType] is represented by a single `u8` as required by the protocol.
///
/// # Example
/// It is usually used like this:
/// ```
/// # use rosenpass_wireguard_broker::api::msgs::{InvalidMessageTypeError, MsgType};
/// let typ: u8 = 0x01; // Usually specifically set or comes out of a message.
/// let typ = MsgType::try_from(typ)?;
/// let MsgType::SetPsk = typ; // Assert type.
/// # Ok::<(), InvalidMessageTypeError>(())
/// ```
#[repr(u8)]
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum MsgType {
SetPsk = 0x01,
}
/// Error indicating that an invalid [MsgType] was used.
/// This error is returned by [MsgType::try_from].
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct InvalidMessageTypeError;
impl TryFrom<u8> for MsgType {
type Error = InvalidMessageTypeError;
/// Parse a [u8] as a [MsgType].
///
/// # Example
/// ```rust
/// use rosenpass_wireguard_broker::api::msgs::MsgType;
/// let msg_type = MsgType::try_from(0x01);
/// assert!(msg_type.is_ok());
/// assert_eq!(msg_type.unwrap(), MsgType::SetPsk);
/// ```
/// # Errors
/// Returns an [InvalidMessageTypeError] if `value` does not correspond to a valid [MsgType].
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0x01 => Ok(MsgType::SetPsk),

View File

@@ -1,10 +1,3 @@
//! Server implementation for the WireGuard broker protocol.
//!
//! This module provides a server implementation that communicates with WireGuard broker clients
//! using a binary protocol. The server handles serialization and deserialization of messages,
//! error handling, and the core interaction flow.
//! Specifically, it handles requests to set a pre-shared key for a wireguard interface.
use std::borrow::BorrowMut;
use rosenpass_secret_memory::{Public, Secret};
@@ -14,16 +7,12 @@ use crate::WireGuardBroker;
use super::config::{NetworkBrokerConfigBuilder, NetworkBrokerConfigErr};
/// Error variants for the [BrokerServer].
#[derive(thiserror::Error, Debug, Clone, Eq, PartialEq)]
pub enum BrokerServerError {
/// Indicates that an unknown request type was encountered.
#[error("No such request type: {}", .0)]
NoSuchRequestType(u8),
/// Indicates that an invalid message was sent.
#[error("Invalid message received.")]
InvalidMessage,
/// Indicates an error when configuration the network broker.
#[error("Network Broker Config error: {0}")]
BrokerError(NetworkBrokerConfigErr),
}
@@ -35,18 +24,11 @@ impl From<msgs::InvalidMessageTypeError> for BrokerServerError {
}
}
/// The broker server. It requires an inner [WireGuardBroker] and an error type such
/// that the [msgs::SetPskError] implements [From] for the error type.
/// # Type Parameters
/// - `Err`: The used error type. Must be chosen such that [msgs::SetPskError] implements
/// [`From<Err>`](From)
///- `Inner`: A [WireGuardBroker]-type parametrized with `Err`.
pub struct BrokerServer<Err, Inner>
where
Inner: WireGuardBroker<Error = Err>,
msgs::SetPskError: From<Err>,
{
/// The inner [WireGuardBroker].
inner: Inner,
}
@@ -56,17 +38,10 @@ where
msgs::SetPskError: From<Err>,
Err: std::fmt::Debug,
{
/// Creates a new [BrokerServer] from a [WireGuardBroker].
pub fn new(inner: Inner) -> Self {
Self { inner }
}
/// Processes a message (at the moment only setting the pre-shared key is supported)
/// and takes the appropriate actions.
///
/// # Errors
/// - [BrokerServerError::InvalidMessage] if the message is not properly formatted or refers to
/// an unsupported message type.
pub fn handle_message(
&mut self,
req: &[u8],
@@ -78,28 +53,22 @@ where
let typ = msgs::MsgType::try_from(*typ)?;
let msgs::MsgType::SetPsk = typ; // Assert type
let req =
zerocopy::Ref::<&[u8], Envelope<SetPskRequest>>::new(req).ok_or(InvalidMessage)?;
let mut res =
zerocopy::Ref::<&mut [u8], Envelope<SetPskResponse>>::new(res).ok_or(InvalidMessage)?;
let req = zerocopy::Ref::<&[u8], Envelope<SetPskRequest>>::new(req)
.ok_or(BrokerServerError::InvalidMessage)?;
let mut res = zerocopy::Ref::<&mut [u8], Envelope<SetPskResponse>>::new(res)
.ok_or(BrokerServerError::InvalidMessage)?;
res.msg_type = msgs::MsgType::SetPsk as u8;
self.handle_set_psk(&req.payload, &mut res.payload)?;
Ok(res.bytes().len())
}
/// Sets the pre-shared key for the interface identified in `req` to the pre-shared key
/// specified in `req`.
///
/// # Errors
/// - [InvalidMessage](BrokerServerError::InvalidMessage) if the `iface` specified in `req` is
/// longer than 255 bytes or not correctly encoded in utf8.
fn handle_set_psk(
&mut self,
req: &SetPskRequest,
res: &mut SetPskResponse,
) -> Result<(), BrokerServerError> {
// Using unwrap here since lenses can not return fixed-size arrays.
// Using unwrap here since lenses can not return fixed-size arrays
// TODO: Slices should give access to fixed size arrays
let peer_id = Public::from_slice(&req.peer_id);
let psk = Secret::from_slice(&req.psk);
@@ -126,60 +95,3 @@ where
Ok(())
}
}
// We can only include this test if this feature is enabled, because otherwise
// brokers::netlink::SetPskError is not defined.
#[cfg(all(feature = "experiment_api", target_os = "linux"))]
#[cfg(test)]
mod tests {
use crate::api::msgs;
use crate::api::msgs::{Envelope, SetPskRequest};
use crate::api::server::BrokerServer;
use crate::brokers::netlink::SetPskError;
use crate::{SerializedBrokerConfig, WireGuardBroker};
use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
use zerocopy::AsBytes;
#[derive(Debug, Clone)]
struct MockWireGuardBroker {
psk: Secret<32>,
}
impl WireGuardBroker for MockWireGuardBroker {
type Error = SetPskError;
fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> Result<(), Self::Error> {
self.psk = config.psk.clone();
Ok(())
}
}
#[test]
fn test_broker_server() {
secret_policy_use_only_malloc_secrets();
let mock_broker = MockWireGuardBroker {
psk: Secret::zero(),
};
let mut broker_server = BrokerServer::new(mock_broker);
let mut iface_buf: [u8; 255] = [0; 255];
// These are the utf encoded bytes of the string "wg0".
iface_buf[0] = 119;
iface_buf[1] = 103;
iface_buf[2] = 48;
let psk_req = SetPskRequest {
psk: [0; 32],
peer_id: [0; 32],
iface_size: 3,
iface_buf,
};
let req: Envelope<SetPskRequest> = Envelope {
msg_type: 0x01, // The only valid value.
reserved: [0, 0, 0],
payload: psk_req,
};
let mut res: [u8; msgs::RESPONSE_MSG_BUFFER_SIZE] = [0; msgs::RESPONSE_MSG_BUFFER_SIZE];
broker_server
.handle_message(req.as_bytes(), &mut res)
.unwrap();
}
}

View File

@@ -1,12 +1,3 @@
//! This module defines the Unix socket broker that interacts with the Linux-specific
//! WireGuard broker through a privileged process.
//!
//! It manages communication using length-prefixed
//! messages that are read from standard-input.
//! On each input message the process responds through its standard-output
//!
//! The functionality is only supported on Linux systems.
fn main() {
#[cfg(target_os = "linux")]
linux::main().unwrap();
@@ -17,33 +8,20 @@ fn main() {
#[cfg(target_os = "linux")]
pub mod linux {
//! Linux-specific implementation for the broker that communicates with the WireGuard broker.
use std::io::{stdin, stdout, Read, Write};
use rosenpass_wireguard_broker::api::msgs;
use rosenpass_wireguard_broker::api::server::BrokerServer;
use rosenpass_wireguard_broker::brokers::netlink as wg;
/// Represents errors that can occur during WireGuard broker operations
#[derive(thiserror::Error, Debug)]
pub enum BrokerAppError {
/// Wraps standard I/O errors that may occur during broker operations
#[error(transparent)]
IoError(#[from] std::io::Error),
/// Wraps WireGuard connection errors
#[error(transparent)]
WgConnectError(#[from] wg::ConnectError),
/// Wraps errors that occur when setting WireGuard Pre-Shared Keys (PSK)
#[error(transparent)]
WgSetPskError(#[from] wg::SetPskError),
/// Indicates that a received message exceeds the maximum allowed size
///
/// # Arguments
/// * `u64` - The size of the oversized message in bytes
#[error("Oversized message {}; something about the request is fatally wrong", .0)]
OversizedMessage(u64),
}

View File

@@ -1,6 +1,3 @@
//! Provides an asynchronous Unix socket handler for managing connections between clients
//! and privileged WireGuard broker processes.
use std::process::Stdio;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
@@ -15,7 +12,6 @@ use clap::{ArgGroup, Parser};
use rosenpass_util::fd::claim_fd;
use rosenpass_wireguard_broker::api::msgs;
/// Command-line arguments for configuring the socket handler
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
#[clap(group(
@@ -49,13 +45,11 @@ struct Args {
command: Vec<String>,
}
/// Represents a request to the broker with a channel for receiving the response
struct BrokerRequest {
reply_to: oneshot::Sender<BrokerResponse>,
request: Vec<u8>,
}
/// Contains the broker's response data
struct BrokerResponse {
response: Vec<u8>,
}
@@ -93,7 +87,6 @@ async fn main() -> Result<()> {
}
}
/// Manages communication with the privileged broker process
async fn direct_broker_process(
mut queue: mpsc::Receiver<BrokerRequest>,
cmd: Vec<String>,
@@ -138,7 +131,6 @@ async fn direct_broker_process(
}
}
/// Accepts and handles incoming client connections
async fn listen_for_clients(queue: mpsc::Sender<BrokerRequest>, sock: UnixListener) -> Result<()> {
loop {
let (stream, _addr) = sock.accept().await?;
@@ -153,7 +145,6 @@ async fn listen_for_clients(queue: mpsc::Sender<BrokerRequest>, sock: UnixListen
// NOTE: If loop can ever terminate we need to join the spawned tasks
}
/// Handles individual client connections and message processing
async fn on_accept(queue: mpsc::Sender<BrokerRequest>, mut stream: UnixStream) -> Result<()> {
let mut req_buf = Vec::new();

View File

@@ -1,52 +1,3 @@
//! Asynchronous WireGuard PSK broker client using mio for non-blocking I/O.
//!
//! This module provides a client implementation that communicates with a WireGuard broker
//! through Unix domain sockets using non-blocking I/O operations. It's designed to be used
//! in event-driven applications using the mio event framework.
//!
//! # Examples
//!
//! ```no_run
//! # use mio::net::UnixStream;
//! # use rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient;
//! # use rosenpass_wireguard_broker::{WireGuardBroker, WireguardBrokerMio};
//! # use mio::{Events, Interest, Poll, Token};
//! # use rosenpass_secret_memory::{Public, Secret};
//! # use rosenpass_wireguard_broker::api::config::NetworkBrokerConfig;
//! # use rosenpass_wireguard_broker::SerializedBrokerConfig;
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let socket = UnixStream::connect("/path/to/broker.sock")?;
//! let mut client = MioBrokerClient::new(socket);
//!
//! // Set up mio polling
//! let mut poll = Poll::new()?;
//! let mut events = Events::with_capacity(128);
//! client.register(&poll.registry(), Token(0))?;
//!
//! // Prepare PSK configuration
//! let network_config = NetworkBrokerConfig {
//! iface: "wg0",
//! peer_id: &Public::zero(), // Replace with actual peer ID
//! psk: &Secret::zero(), // Replace with actual PSK
//! };
//!
//! // Convert to serialized format and send
//! let config: SerializedBrokerConfig = network_config.into();
//! client.set_psk(config)?;
//!
//! // Process responses in event loop
//! loop {
//! poll.poll(&mut events, None)?;
//! for event in &events {
//! if event.token() == Token(0) {
//! client.process_poll()?;
//! }
//! }
//! }
//! # Ok(())
//! # }
//! ```
use anyhow::{bail, Context};
use mio::Interest;
use rosenpass_secret_memory::Secret;
@@ -62,44 +13,12 @@ use crate::api::client::{
};
use crate::{SerializedBrokerConfig, WireGuardBroker, WireguardBrokerMio};
/// WireGuard broker client using mio for non-blocking I/O operations.
///
/// This client communicates with a WireGuard broker through a Unix domain socket,
/// using length-prefixed messages for communication. It supports both the basic
/// `WireGuardBroker` operations and non-blocking I/O through the
/// `WireguardBrokerMio` trait.
///
/// # Examples
///
/// ```no_run
/// use mio::net::UnixStream;
/// use rosenpass_wireguard_broker::brokers::mio_client::MioBrokerClient;
/// use rosenpass_wireguard_broker::{WireGuardBroker, SerializedBrokerConfig};
/// use rosenpass_secret_memory::{Public, Secret};
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let socket = UnixStream::connect("/path/to/broker.sock")?;
/// let mut client = MioBrokerClient::new(socket);
///
/// // Set a PSK
/// let config = SerializedBrokerConfig {
/// interface: "wg0".as_bytes(),
/// peer_id: &Public::zero(), // Replace with actual peer ID
/// psk: &Secret::zero(), // Replace with actual PSK
/// additional_params: &[],
/// };
///
/// client.set_psk(config)?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct MioBrokerClient {
inner: BrokerClient<MioBrokerClientIo>,
mio_token: Option<mio::Token>,
}
/// A buffer wrapper that provides secure memory for sensitive data.
#[derive(Debug)]
struct SecretBuffer<const N: usize>(pub Secret<N>);
@@ -124,10 +43,6 @@ impl<const N: usize> BorrowMut<[u8]> for SecretBuffer<N> {
type ReadBuffer = LengthPrefixDecoder<SecretBuffer<4096>>;
type WriteBuffer = LengthPrefixEncoder<SecretBuffer<4096>>;
/// I/O implementation for the broker client using non-blocking operations.
///
/// This type handles the low-level details of sending and receiving length-prefixed
/// messages over a Unix domain socket.
#[derive(Debug)]
struct MioBrokerClientIo {
socket: mio::net::UnixStream,
@@ -136,10 +51,6 @@ struct MioBrokerClientIo {
}
impl MioBrokerClient {
/// Creates a new client from a Unix domain socket.
///
/// The socket should be connected to a WireGuard broker server that speaks
/// the same protocol.
pub fn new(socket: mio::net::UnixStream) -> Self {
let read_buffer = LengthPrefixDecoder::new(SecretBuffer::new());
let write_buffer = LengthPrefixEncoder::from_buffer(SecretBuffer::new());
@@ -155,10 +66,6 @@ impl MioBrokerClient {
}
}
/// Polls for and processes any pending responses from the broker.
///
/// This method should be called when the socket becomes readable according
/// to mio events.
fn poll(&mut self) -> anyhow::Result<()> {
self.inner.io_mut().flush()?;

View File

@@ -1,35 +1,3 @@
//! Native Unix implementation of the WireGuard PSK broker using the `wg` command-line tool.
//!
//! This module provides an implementation that works on Unix systems by executing the `wg`
//! command-line tool to set pre-shared keys. It requires the `wg` tool to be installed and
//! accessible in the system PATH.
//!
//! # Examples
//!
//! ```no_run
//! use rosenpass_secret_memory::{Public, Secret};
//! use rosenpass_wireguard_broker::brokers::native_unix::{NativeUnixBroker, NativeUnixBrokerConfigBase};
//! use rosenpass_wireguard_broker::{WireGuardBroker, WireguardBrokerCfg, WG_KEY_LEN, WG_PEER_LEN};
//!
//! # fn main() -> Result<(), anyhow::Error> {
//! // Create a broker instance
//! let mut broker = NativeUnixBroker::new();
//!
//! // Create configuration
//! let config = NativeUnixBrokerConfigBase {
//! interface: "wg0".to_string(),
//! peer_id: Public::zero(), // Replace with actual peer ID
//! extra_params: Vec::new(),
//! };
//!
//! // Set PSK using the broker
//! let psk = Secret::<WG_KEY_LEN>::zero(); // Replace with actual PSK
//! let serialized_config = config.create_config(&psk);
//! broker.set_psk(serialized_config)?;
//! # Ok(())
//! # }
//! ```
use std::fmt::Debug;
use std::process::{Command, Stdio};
use std::thread;
@@ -44,21 +12,9 @@ use rosenpass_util::{b64::B64Display, file::StoreValueB64Writer};
use crate::{SerializedBrokerConfig, WireGuardBroker, WireguardBrokerCfg, WireguardBrokerMio};
use crate::{WG_KEY_LEN, WG_PEER_LEN};
/// Maximum size of a base64-encoded WireGuard key in bytes
const MAX_B64_KEY_SIZE: usize = WG_KEY_LEN * 5 / 3;
/// Maximum size of a base64-encoded WireGuard peer ID in bytes
const MAX_B64_PEER_ID_SIZE: usize = WG_PEER_LEN * 5 / 3;
/// A WireGuard broker implementation that uses the native `wg` command-line tool.
///
/// This broker executes the `wg` command to set pre-shared keys. It supports both synchronous
/// operations through the `WireGuardBroker` trait and asynchronous operations through the
/// `WireguardBrokerMio` trait.
///
/// # Requirements
///
/// - The `wg` command-line tool must be installed and in the system PATH
/// - The user running the broker must have sufficient permissions to execute `wg` commands
#[derive(Debug)]
pub struct NativeUnixBroker {
mio_token: Option<mio::Token>,
@@ -154,63 +110,16 @@ impl WireguardBrokerMio for NativeUnixBroker {
}
}
/// Base configuration for the native Unix WireGuard broker.
///
/// This configuration type is used to store persistent broker settings and create
/// serialized configurations for individual PSK operations.
///
/// # Examples
///
/// ```
/// use rosenpass_wireguard_broker::brokers::native_unix::NativeUnixBrokerConfigBase;
/// use rosenpass_secret_memory::Public;
/// use rosenpass_wireguard_broker::WG_PEER_LEN;
///
/// let config = NativeUnixBrokerConfigBase {
/// interface: "wg0".to_string(),
/// peer_id: Public::zero(),
/// extra_params: Vec::new(),
/// };
/// ```
#[derive(Debug, Builder)]
#[builder(pattern = "mutable")]
pub struct NativeUnixBrokerConfigBase {
/// Name of the WireGuard interface (e.g., "wg0")
pub interface: String,
/// Public key of the peer
pub peer_id: Public<WG_PEER_LEN>,
/// Additional parameters to pass to the wg command
#[builder(private)]
pub extra_params: Vec<u8>,
}
impl NativeUnixBrokerConfigBaseBuilder {
/// Sets the peer ID from a base64-encoded string.
///
/// # Arguments
///
/// * `peer_id` - Base64-encoded peer public key
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use rosenpass_wireguard_broker::brokers::native_unix::{NativeUnixBrokerConfigBaseBuilder};
/// let mut peer_cfg = NativeUnixBrokerConfigBaseBuilder::default();
/// // set peer id to [48;32] encoded as base64
/// peer_cfg.peer_id_b64("MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=")?;
/// peer_cfg.interface("wg0".to_string());
/// peer_cfg.extra_params_ser(&vec![])?;
/// let peer_cfg = peer_cfg.build()?;
/// assert_eq!(peer_cfg.peer_id.value, [48u8;32]);
///
/// let error = NativeUnixBrokerConfigBaseBuilder::default()
/// .peer_id_b64("MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA") // invalid base64 encoding
/// .err().unwrap();
/// assert_eq!(error.to_string(), "Failed to parse peer id b64");
/// # Ok(())
/// # }
/// ```
pub fn peer_id_b64(
&mut self,
peer_id: &str,
@@ -224,29 +133,6 @@ impl NativeUnixBrokerConfigBaseBuilder {
Ok(self.peer_id(peer_id_b64))
}
/// Sets additional parameters for the wg command.
///
/// Note: This function cannot fail as `Vec<String>` is always serializable.
///
/// # Examples
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// use rosenpass_wireguard_broker::brokers::native_unix::NativeUnixBrokerConfigBaseBuilder;
///
/// let mut peer_cfg = NativeUnixBrokerConfigBaseBuilder::default();
/// // Set typical wireguard parameters
/// peer_cfg.interface("wg0".to_string());
/// peer_cfg.peer_id_b64("Zm9v")?;
/// peer_cfg.extra_params_ser(&vec![
/// "persistent-keepalive".to_string(),
/// "25".to_string(),
/// "allowed-ips".to_string(),
/// "10.0.0.2/32".to_string(),
/// ])?;
/// let peer_cfg = peer_cfg.build()?;
/// # Ok(())
/// # }
/// ```
pub fn extra_params_ser(
&mut self,
extra_params: &Vec<String>,
@@ -271,17 +157,12 @@ impl WireguardBrokerCfg for NativeUnixBrokerConfigBase {
}
}
/// Runtime configuration for a single PSK operation.
#[derive(Debug, Builder)]
#[builder(pattern = "mutable")]
pub struct NativeUnixBrokerConfig<'a> {
/// WireGuard interface name
pub interface: &'a str,
/// Public key of the peer
pub peer_id: &'a Public<WG_PEER_LEN>,
/// Pre-shared key to set
pub psk: &'a Secret<WG_KEY_LEN>,
/// Additional wg command parameters
pub extra_params: Vec<String>,
}

View File

@@ -1,31 +1,4 @@
#![cfg(target_os = "linux")]
//! Linux-specific WireGuard PSK broker implementation using netlink.
//!
//! This module provides direct kernel communication through netlink sockets for managing
//! WireGuard pre-shared keys. It's more efficient than the command-line implementation
//! but only available on Linux systems.
//!
//! # Examples
//!
//! ```no_run
//! use rosenpass_secret_memory::{Public, Secret};
//! use rosenpass_wireguard_broker::{WireGuardBroker, SerializedBrokerConfig, WG_KEY_LEN, WG_PEER_LEN};
//! use rosenpass_wireguard_broker::brokers::netlink::NetlinkWireGuardBroker;
//! # use rosenpass_secret_memory::secret_policy_use_only_malloc_secrets;
//! # secret_policy_use_only_malloc_secrets();
//!
//! let mut broker = NetlinkWireGuardBroker::new()?;
//!
//! let config = SerializedBrokerConfig {
//! interface: "wg0".as_bytes(),
//! peer_id: &Public::zero(), // Replace with actual peer ID
//! psk: &Secret::zero(), // Replace with actual PSK
//! additional_params: &[],
//! };
//!
//! broker.set_psk(config)?;
//! # Ok::<(), anyhow::Error>(())
//! ```
use std::fmt::Debug;
@@ -35,14 +8,12 @@ use crate::api::config::NetworkBrokerConfig;
use crate::api::msgs;
use crate::{SerializedBrokerConfig, WireGuardBroker};
/// Error that can occur when connecting to the WireGuard netlink interface.
#[derive(thiserror::Error, Debug)]
pub enum ConnectError {
#[error(transparent)]
ConnectError(#[from] wg::err::ConnectError),
}
/// Errors that can occur during netlink operations.
#[derive(thiserror::Error, Debug)]
pub enum NetlinkError {
#[error(transparent)]
@@ -51,7 +22,6 @@ pub enum NetlinkError {
GetDevice(#[from] wg::err::GetDeviceError),
}
/// Errors that can occur when setting a pre-shared key.
#[derive(thiserror::Error, Debug)]
pub enum SetPskError {
#[error("The indicated wireguard interface does not exist")]
@@ -62,41 +32,18 @@ pub enum SetPskError {
NetlinkError(#[from] NetlinkError),
}
/// # Example
/// ```
/// # use wireguard_uapi::err::NlError;
/// # use wireguard_uapi::linux::err::SetDeviceError;
/// use rosenpass_wireguard_broker::brokers::netlink::SetPskError;
/// let set_device_error: SetDeviceError = SetDeviceError::NlError(NlError::Msg("test-error".to_string()));
/// let set_psk_error: SetPskError = set_device_error.into();
/// ```
impl From<wg::err::SetDeviceError> for SetPskError {
fn from(err: wg::err::SetDeviceError) -> Self {
NetlinkError::from(err).into()
}
}
/// # Example
/// ```
/// # use wireguard_uapi::err::NlError;
/// # use wireguard_uapi::linux::err::GetDeviceError;
/// # use rosenpass_wireguard_broker::brokers::netlink::SetPskError;
/// let get_device_error: GetDeviceError = GetDeviceError::NlError(NlError::Msg("test-error".to_string()));
/// let set_psk_error: SetPskError = get_device_error.into();
/// ```
impl From<wg::err::GetDeviceError> for SetPskError {
fn from(err: wg::err::GetDeviceError) -> Self {
NetlinkError::from(err).into()
}
}
/// # Example
/// ```
/// use rosenpass_wireguard_broker::api::msgs::SetPskError as SetPskMsgsError;
/// use rosenpass_wireguard_broker::brokers::netlink::SetPskError as SetPskNetlinkError;
/// let set_psk_nlink_error: SetPskNetlinkError = SetPskNetlinkError::NoSuchInterface;
/// let set_psk_msgs_error = SetPskMsgsError::from(set_psk_nlink_error);
/// ```
use msgs::SetPskError as SetPskMsgsError;
use SetPskError as SetPskNetlinkError;
impl From<SetPskNetlinkError> for SetPskMsgsError {
@@ -108,33 +55,11 @@ impl From<SetPskNetlinkError> for SetPskMsgsError {
}
}
/// WireGuard broker implementation using Linux netlink sockets.
///
/// This implementation communicates directly with the kernel through netlink sockets,
/// providing better performance than command-line based implementations.
///
/// # Examples
///
/// ```
/// use rosenpass_wireguard_broker::brokers::netlink::NetlinkWireGuardBroker;
/// use rosenpass_wireguard_broker::WireGuardBroker;
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let mut broker = NetlinkWireGuardBroker::new()?;
/// # Ok(())
/// # }
/// ```
///
/// # Platform Support
///
/// This implementation is only available on Linux systems and requires appropriate
/// permissions to use netlink sockets.
pub struct NetlinkWireGuardBroker {
sock: wg::WgSocket,
}
impl NetlinkWireGuardBroker {
/// Opens a netlink socket to the WireGuard kernel module
/// and returns a new netlink-based WireGuard broker.
pub fn new() -> Result<Self, ConnectError> {
let sock = wg::WgSocket::connect()?;
Ok(Self { sock })
@@ -187,27 +112,3 @@ impl WireGuardBroker for NetlinkWireGuardBroker {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Public, Secret};
#[test]
fn smoke_test() -> Result<(), Box<dyn std::error::Error>> {
secret_policy_use_only_malloc_secrets();
let result = NetlinkWireGuardBroker::new();
assert!(result.is_ok());
let mut broker = result.unwrap();
let peer_id = Public::zero();
let psk = Secret::zero();
let config: SerializedBrokerConfig = NetworkBrokerConfig {
iface: "wg0",
peer_id: &peer_id,
psk: &psk,
}
.into();
let result = broker.set_psk(config);
assert!(result.is_err());
Ok(())
}
}

View File

@@ -1,106 +1,38 @@
//! A broker interface for managing WireGuard pre-shared keys (PSK).
//!
//! This crate provides traits and implementations for interacting with WireGuard interfaces
//! to set pre-shared keys for peers. It supports different backend implementations including:
//! - Native Unix command-line interface
//! - Linux netlink interface
//! - Custom Unix socket protocol
//!
//! # Examples
//!
//! ```no_run
//! # use rosenpass_secret_memory::{Public, Secret};
//! # use rosenpass_wireguard_broker::{WireGuardBroker, SerializedBrokerConfig, WG_KEY_LEN, WG_PEER_LEN};
//! # use std::error::Error;
//!
//! # fn main() -> Result<(), Box<dyn Error>> {
//! # rosenpass_secret_memory::policy::secret_policy_try_use_memfd_secrets();
//! # let interface = "wg0";
//! # let peer_id = Public::<WG_PEER_LEN>::zero();
//! # let psk = Secret::<WG_KEY_LEN>::zero();
//!
//! // Create a native Unix broker
//! let mut broker = rosenpass_wireguard_broker::brokers::native_unix::NativeUnixBroker::new();
//!
//! // Configure and set PSK
//! let config = SerializedBrokerConfig {
//! interface: interface.as_bytes(),
//! peer_id: &peer_id,
//! psk: &psk,
//! additional_params: &[],
//! };
//!
//! broker.set_psk(config)?;
//! # Ok(())
//! # }
//! ```
use rosenpass_secret_memory::{Public, Secret};
use std::fmt::Debug;
/// Length of a WireGuard key in bytes
pub const WG_KEY_LEN: usize = 32;
/// Length of a WireGuard peer ID in bytes
pub const WG_PEER_LEN: usize = 32;
/// Core trait for WireGuard PSK brokers.
///
/// This trait defines the basic interface for setting pre-shared keys (PSK) on WireGuard interfaces.
/// Implementations handle the actual communication with WireGuard, whether through command-line tools,
/// netlink, or other mechanisms.
pub trait WireGuardBroker: Debug {
/// The error type returned by broker operations
type Error;
/// Set a pre-shared key for a WireGuard peer
fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> Result<(), Self::Error>;
}
/// Configuration trait for WireGuard PSK brokers.
///
/// This trait allows creation of broker configurations from a PSK and implementation-specific
/// configuration data.
pub trait WireguardBrokerCfg: Debug {
/// Creates a serialized broker configuration from this config and a specific PSK
fn create_config<'a>(&'a self, psk: &'a Secret<WG_KEY_LEN>) -> SerializedBrokerConfig<'a>;
}
/// Serialized configuration for WireGuard PSK operations.
#[derive(Debug)]
pub struct SerializedBrokerConfig<'a> {
/// The WireGuard interface name as UTF-8 bytes
pub interface: &'a [u8],
/// The public key of the peer
pub peer_id: &'a Public<WG_PEER_LEN>,
/// The pre-shared key to set
pub psk: &'a Secret<WG_KEY_LEN>,
/// Additional implementation-specific parameters
pub additional_params: &'a [u8],
}
/// Extension trait for mio integration with WireGuard brokers.
///
/// This trait extends the basic `WireGuardBroker` functionality with asynchronous I/O
/// operations using the mio event framework.
pub trait WireguardBrokerMio: WireGuardBroker {
/// The error type for mio operations
type MioError;
/// Register the broker with a mio Registry for event notifications
/// Register interested events for mio::Registry
fn register(
&mut self,
registry: &mio::Registry,
token: mio::Token,
) -> Result<(), Self::MioError>;
/// Get the mio token associated with this broker, if any
fn mio_token(&self) -> Option<mio::Token>;
/// Process events after a mio poll operation
/// Run after a mio::poll operation
fn process_poll(&mut self) -> Result<(), Self::MioError>;
/// Unregister the broker from a mio Registry
fn unregister(&mut self, registry: &mio::Registry) -> Result<(), Self::MioError>;
}