chore(API, AppServer): Deal with CryptoServer being uninit.

Before this, we would just raise an error.
This commit is contained in:
Karolin Varner
2024-08-09 21:07:59 +02:00
parent 5f6c36e773
commit 48f7ff93e3
9 changed files with 498 additions and 25 deletions

View File

@@ -8,6 +8,7 @@ use mio::Interest;
use mio::Token;
use rosenpass_secret_memory::Public;
use rosenpass_secret_memory::Secret;
use rosenpass_util::build::ConstructionSite;
use rosenpass_util::file::StoreValueB64;
use rosenpass_wireguard_broker::WireguardBrokerMio;
use rosenpass_wireguard_broker::{WireguardBrokerCfg, WG_KEY_LEN};
@@ -31,6 +32,7 @@ use std::slice;
use std::time::Duration;
use std::time::Instant;
use crate::protocol::BuildCryptoServer;
use crate::protocol::HostIdentification;
use crate::{
config::Verbosity,
@@ -147,7 +149,7 @@ pub struct AppServerTest {
// TODO add user control via unix domain socket and stdin/stdout
#[derive(Debug)]
pub struct AppServer {
pub crypt: Option<CryptoServer>,
pub crypto_site: ConstructionSite<BuildCryptoServer, CryptoServer>,
pub sockets: Vec<mio::net::UdpSocket>,
pub events: mio::Events,
pub mio_poll: mio::Poll,
@@ -606,7 +608,7 @@ impl AppServer {
// TODO use mio::net::UnixStream together with std::os::unix::net::UnixStream for Linux
Ok(Self {
crypt: Some(CryptoServer::new(sk, pk)),
crypto_site: ConstructionSite::from_product(CryptoServer::new(sk, pk)),
peers: Vec::new(),
verbosity,
sockets,
@@ -627,14 +629,14 @@ impl AppServer {
}
pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> {
self.crypt
.as_ref()
self.crypto_site
.product_ref()
.context("Cryptography handler not initialized")
}
pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> {
self.crypt
.as_mut()
self.crypto_site
.product_mut()
.context("Cryptography handler not initialized")
}
@@ -688,8 +690,13 @@ impl AppServer {
broker_peer: Option<BrokerPeer>,
hostname: Option<String>,
) -> anyhow::Result<AppPeerPtr> {
let PeerPtr(pn) = self.crypto_server_mut()?.add_peer(psk, pk)?;
let PeerPtr(pn) = match &mut self.crypto_site {
ConstructionSite::Void => bail!("Crypto server construction site is void"),
ConstructionSite::Builder(builder) => builder.add_peer(psk, pk),
ConstructionSite::Product(srv) => srv.add_peer(psk, pk)?,
};
assert!(pn == self.peers.len());
let initial_endpoint = hostname
.map(Endpoint::discovery_from_hostname)
.transpose()?;
@@ -774,16 +781,31 @@ impl AppServer {
}
}
match self.poll(&mut *rx)? {
#[allow(clippy::redundant_closure_call)]
SendInitiation(peer) => tx_maybe_with!(peer, || self
enum CryptoSrv {
Avail,
Missing,
}
let poll_result = self.poll(&mut *rx)?;
let have_crypto = match self.crypto_site.is_available() {
true => CryptoSrv::Avail,
false => CryptoSrv::Missing,
};
#[allow(clippy::redundant_closure_call)]
match (have_crypto, poll_result) {
(CryptoSrv::Missing, SendInitiation(_)) => {}
(CryptoSrv::Avail, SendInitiation(peer)) => tx_maybe_with!(peer, || self
.crypto_server_mut()?
.initiate_handshake(peer.lower(), &mut *tx))?,
#[allow(clippy::redundant_closure_call)]
SendRetransmission(peer) => tx_maybe_with!(peer, || self
(CryptoSrv::Missing, SendRetransmission(_)) => {}
(CryptoSrv::Avail, SendRetransmission(peer)) => tx_maybe_with!(peer, || self
.crypto_server_mut()?
.retransmit_handshake(peer.lower(), &mut *tx))?,
DeleteKey(peer) => {
(CryptoSrv::Missing, DeleteKey(_)) => {}
(CryptoSrv::Avail, DeleteKey(peer)) => {
self.output_key(peer, Stale, &SymKey::random())?;
// There was a loss of connection apparently; restart host discovery
@@ -797,7 +819,8 @@ impl AppServer {
);
}
ReceivedMessage(len, endpoint) => {
(CryptoSrv::Missing, ReceivedMessage(_, _)) => {}
(CryptoSrv::Avail, ReceivedMessage(len, endpoint)) => {
let msg_result = match self.under_load {
DoSOperation::UnderLoad => {
self.handle_msg_under_load(&endpoint, &rx[..len], &mut *tx)
@@ -910,17 +933,32 @@ impl AppServer {
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
use crate::protocol::PollResult as C;
use AppPollResult as A;
loop {
return Ok(match self.crypto_server_mut()?.poll()? {
C::DeleteKey(PeerPtr(no)) => A::DeleteKey(AppPeerPtr(no)),
C::SendInitiation(PeerPtr(no)) => A::SendInitiation(AppPeerPtr(no)),
C::SendRetransmission(PeerPtr(no)) => A::SendRetransmission(AppPeerPtr(no)),
C::Sleep(timeout) => match self.try_recv(rx_buf, timeout)? {
Some((len, addr)) => A::ReceivedMessage(len, addr),
None => continue,
},
});
}
let res = loop {
// Call CryptoServer's poll (if available)
let crypto_poll = self
.crypto_site
.product_mut()
.map(|crypto| crypto.poll())
.transpose()?;
// Map crypto server's poll result to our poll result
let io_poll_timeout = match crypto_poll {
Some(C::DeleteKey(PeerPtr(no))) => break A::DeleteKey(AppPeerPtr(no)),
Some(C::SendInitiation(PeerPtr(no))) => break A::SendInitiation(AppPeerPtr(no)),
Some(C::SendRetransmission(PeerPtr(no))) => {
break A::SendRetransmission(AppPeerPtr(no))
}
Some(C::Sleep(timeout)) => timeout, // No event from crypto-server, do IO
None => crate::protocol::UNENDING, // Crypto server is uninitialized, do IO
};
// Perform IO (look for a message)
if let Some((len, addr)) = self.try_recv(rx_buf, io_poll_timeout)? {
break A::ReceivedMessage(len, addr);
}
};
Ok(res)
}
/// Tries to receive a new message

View File

@@ -0,0 +1,127 @@
use rosenpass_util::{
build::Build,
mem::{DiscardResultExt, SwapWithDefaultExt},
result::ensure_or,
};
use thiserror::Error;
use super::{CryptoServer, PeerPtr, SPk, SSk, SymKey};
#[derive(Debug, Clone)]
pub struct Keypair {
pub sk: SSk,
pub pk: SPk,
}
// TODO: We need a named tuple derive
impl Keypair {
pub fn new(sk: SSk, pk: SPk) -> Self {
Self { sk, pk }
}
pub fn zero() -> Self {
Self::new(SSk::zero(), SPk::zero())
}
pub fn random() -> Self {
Self::new(SSk::random(), SPk::random())
}
pub fn from_parts(parts: (SSk, SPk)) -> Self {
Self::new(parts.0, parts.1)
}
pub fn into_parts(self) -> (SSk, SPk) {
(self.sk, self.pk)
}
}
#[derive(Error, Debug)]
#[error("PSK already set in BuildCryptoServer")]
pub struct PskAlreadySet;
#[derive(Error, Debug)]
#[error("Keypair already set in BuildCryptoServer")]
pub struct KeypairAlreadySet;
#[derive(Error, Debug)]
#[error("Can not construct CryptoServer: Missing keypair")]
pub struct MissingKeypair;
#[derive(Debug, Default)]
pub struct BuildCryptoServer {
pub keypair: Option<Keypair>,
pub peers: Vec<PeerParams>,
}
impl Build<CryptoServer> for BuildCryptoServer {
type Error = anyhow::Error;
fn build(self) -> Result<CryptoServer, Self::Error> {
let Some(Keypair { sk, pk }) = self.keypair else {
return Err(MissingKeypair)?;
};
let mut srv = CryptoServer::new(sk, pk);
for (idx, PeerParams { psk, pk }) in self.peers.into_iter().enumerate() {
let PeerPtr(idx2) = srv.add_peer(psk, pk)?;
assert!(idx == idx2, "Peer id changed during CryptoServer construction from {idx} to {idx2}. This is a developer error.")
}
Ok(srv)
}
}
#[derive(Debug)]
pub struct PeerParams {
pub psk: Option<SymKey>,
pub pk: SPk,
}
impl BuildCryptoServer {
pub fn new(keypair: Option<Keypair>, peers: Vec<PeerParams>) -> Self {
Self { keypair, peers }
}
pub fn empty() -> Self {
Self::new(None, Vec::new())
}
pub fn from_parts(parts: (Option<Keypair>, Vec<PeerParams>)) -> Self {
Self {
keypair: parts.0,
peers: parts.1,
}
}
pub fn take_parts(&mut self) -> (Option<Keypair>, Vec<PeerParams>) {
(self.keypair.take(), self.peers.swap_with_default())
}
pub fn into_parts(mut self) -> (Option<Keypair>, Vec<PeerParams>) {
self.take_parts()
}
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)
}
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
}
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
}
pub fn emancipate(&mut self) -> Self {
Self::from_parts(self.take_parts())
}
}

View File

@@ -0,0 +1,6 @@
mod build_crypto_server;
#[allow(clippy::module_inception)]
mod protocol;
pub use build_crypto_server::*;
pub use protocol::*;

169
util/src/build.rs Normal file
View File

@@ -0,0 +1,169 @@
use crate::{
functional::ApplyExt,
mem::{SwapWithDefaultExt, SwapWithExt},
};
#[derive(thiserror::Error, Debug)]
pub enum ConstructionSiteErectError<E> {
#[error("Construction site is void")]
IsVoid,
#[error("Construction is already built")]
AlreadyBuilt,
#[error("Other construction site error {0:?}")]
Other(#[from] E),
}
pub trait Build<T>: Sized {
type Error;
fn build(self) -> Result<T, Self::Error>;
}
#[derive(Debug)]
pub enum ConstructionSite<Builder, T>
where
Builder: Build<T>,
{
Void,
Builder(Builder),
Product(T),
}
impl<Builder, T> Default for ConstructionSite<Builder, T>
where
Builder: Build<T>,
{
fn default() -> Self {
Self::Void
}
}
impl<Builder, T> ConstructionSite<Builder, T>
where
Builder: Build<T>,
{
pub fn void() -> Self {
Self::Void
}
pub fn new(builder: Builder) -> Self {
Self::Builder(builder)
}
pub fn from_product(value: T) -> Self {
Self::Product(value)
}
pub fn take(&mut self) -> Self {
self.swap_with_default()
}
pub fn modify_taken_with_return<R, F>(&mut self, f: F) -> R
where
F: FnOnce(Self) -> (Self, R),
{
let (site, res) = self.take().apply(f);
self.swap_with(site);
res
}
pub fn modify_taken<F>(&mut self, f: F)
where
F: FnOnce(Self) -> Self,
{
self.take().apply(f).swap_with_mut(self)
}
#[allow(clippy::result_unit_err)]
pub fn erect(&mut self) -> Result<(), ConstructionSiteErectError<Builder::Error>> {
self.modify_taken_with_return(|site| {
let builder = match site {
site @ Self::Void => return (site, Err(ConstructionSiteErectError::IsVoid)),
site @ Self::Product(_) => {
return (site, Err(ConstructionSiteErectError::AlreadyBuilt))
}
Self::Builder(builder) => builder,
};
let product = match builder.build() {
Err(e) => {
return (Self::void(), Err(ConstructionSiteErectError::Other(e)));
}
Ok(p) => p,
};
(Self::from_product(product), Ok(()))
})
}
/// Returns `true` if the construction site is [`Void`].
///
/// [`Void`]: ConstructionSite::Void
#[must_use]
pub fn is_void(&self) -> bool {
matches!(self, Self::Void)
}
/// Returns `true` if the construction site is [`InProgress`].
///
/// [`InProgress`]: ConstructionSite::InProgress
#[must_use]
pub fn in_progess(&self) -> bool {
matches!(self, Self::Builder(..))
}
/// Returns `true` if the construction site is [`Done`].
///
/// [`Done`]: ConstructionSite::Done
#[must_use]
pub fn is_available(&self) -> bool {
matches!(self, Self::Product(..))
}
pub fn into_builder(self) -> Option<Builder> {
use ConstructionSite as S;
match self {
S::Builder(v) => Some(v),
_ => None,
}
}
pub fn builder_ref(&self) -> Option<&Builder> {
use ConstructionSite as S;
match self {
S::Builder(v) => Some(v),
_ => None,
}
}
pub fn builder_mut(&mut self) -> Option<&mut Builder> {
use ConstructionSite as S;
match self {
S::Builder(v) => Some(v),
_ => None,
}
}
pub fn into_product(self) -> Option<T> {
use ConstructionSite as S;
match self {
S::Product(v) => Some(v),
_ => None,
}
}
pub fn product_ref(&self) -> Option<&T> {
use ConstructionSite as S;
match self {
S::Product(v) => Some(v),
_ => None,
}
}
pub fn product_mut(&mut self) -> Option<&mut T> {
use ConstructionSite as S;
match self {
S::Product(v) => Some(v),
_ => None,
}
}
}

View File

@@ -6,6 +6,32 @@ where
v
}
pub trait MutatingExt {
fn mutating<F>(self, f: F) -> Self
where
F: Fn(&mut Self);
fn mutating_mut<F>(&mut self, f: F) -> &mut Self
where
F: Fn(&mut Self);
}
impl<T> MutatingExt for T {
fn mutating<F>(self, f: F) -> Self
where
F: Fn(&mut Self),
{
mutating(self, f)
}
fn mutating_mut<F>(&mut self, f: F) -> &mut Self
where
F: Fn(&mut Self),
{
f(self);
self
}
}
pub fn sideeffect<T, F>(v: T, f: F) -> T
where
F: Fn(&T),
@@ -14,6 +40,58 @@ where
v
}
pub trait SideffectExt {
fn sideeffect<F>(self, f: F) -> Self
where
F: Fn(&Self);
fn sideeffect_ref<F>(&self, f: F) -> &Self
where
F: Fn(&Self);
fn sideeffect_mut<F>(&mut self, f: F) -> &mut Self
where
F: Fn(&Self);
}
impl<T> SideffectExt for T {
fn sideeffect<F>(self, f: F) -> Self
where
F: Fn(&Self),
{
sideeffect(self, f)
}
fn sideeffect_ref<F>(&self, f: F) -> &Self
where
F: Fn(&Self),
{
f(self);
self
}
fn sideeffect_mut<F>(&mut self, f: F) -> &mut Self
where
F: Fn(&Self),
{
f(self);
self
}
}
pub fn run<R, F: FnOnce() -> R>(f: F) -> R {
f()
}
pub trait ApplyExt: Sized {
fn apply<R, F>(self, f: F) -> R
where
F: FnOnce(Self) -> R;
}
impl<T: Sized> ApplyExt for T {
fn apply<R, F>(self, f: F) -> R
where
F: FnOnce(Self) -> R,
{
f(self)
}
}

View File

@@ -1,6 +1,7 @@
#![recursion_limit = "256"]
pub mod b64;
pub mod build;
pub mod fd;
pub mod file;
pub mod functional;

View File

@@ -92,3 +92,47 @@ impl<T> Drop for Forgetting<T> {
forget(value)
}
}
pub trait DiscardResultExt {
fn discard_result(self);
}
impl<T> DiscardResultExt for T {
fn discard_result(self) {}
}
pub trait ForgetExt {
fn forget(self);
}
impl<T> ForgetExt for T {
fn forget(self) {
std::mem::forget(self)
}
}
pub trait SwapWithExt {
fn swap_with(&mut self, other: Self) -> Self;
fn swap_with_mut(&mut self, other: &mut Self);
}
impl<T> SwapWithExt for T {
fn swap_with(&mut self, mut other: Self) -> Self {
self.swap_with_mut(&mut other);
other
}
fn swap_with_mut(&mut self, other: &mut Self) {
std::mem::swap(self, other)
}
}
pub trait SwapWithDefaultExt {
fn swap_with_default(&mut self) -> Self;
}
impl<T: Default> SwapWithDefaultExt for T {
fn swap_with_default(&mut self) -> Self {
self.swap_with(Self::default())
}
}

View File

@@ -8,6 +8,16 @@ macro_rules! attempt {
};
}
pub trait OkExt<E>: Sized {
fn ok(self) -> Result<Self, E>;
}
impl<T, E> OkExt<E> for T {
fn ok(self) -> Result<Self, E> {
Ok(self)
}
}
/// Trait for container types that guarantee successful unwrapping.
///
/// The `.guaranteed()` function can be used over unwrap to show that