mirror of
https://github.com/rosenpass/rosenpass.git
synced 2025-12-18 13:24:38 +03:00
Compare commits
4 Commits
dev/blipp/
...
docu-tests
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caf91c84f0 | ||
|
|
f5b4c17011 | ||
|
|
12506e5f95 | ||
|
|
965600212d |
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -50,8 +50,6 @@ rustPlatform.buildRustPackage {
|
||||
cargoBuildOptions = [ "--package" package ];
|
||||
cargoTestOptions = [ "--package" package ];
|
||||
|
||||
buildFeatures = [ "experiment_api" ];
|
||||
|
||||
doCheck = true;
|
||||
|
||||
cargoLock = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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())?;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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()
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(), []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
/// Module that handles decoding functionality
|
||||
pub mod decoder;
|
||||
/// Module that handles encoding functionality
|
||||
pub mod encoder;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user