Compare commits

...

15 Commits

Author SHA1 Message Date
wucke13
c2f8f9006a add control socket serde code 2023-10-19 23:40:08 +02:00
wucke13
c072b7825f add uds control socket to rosenpass
- add new, optional configuration parameter to the config file, `control_socket`
- enhance debug and trace logging in `app_server.rs`
- add optional attribute `maybe_control_socket` to `AppServer`
- registers the uds (if present) in mio, so that one `mio::poll` call can both check on control commands and normal handshake traffic
- sprinkle a little more documentation over `app_server.rs`
- inject control socket handling skeleton code to `AppServer::try_recv`
  - control socket is always processed first, then incoming traffic
2023-09-23 13:21:58 +02:00
Emil Engler
b7a76849b7 test: Ensure 8MiB of stack size for key generation
This commit ensures that the call to `StaticKEM::keygen` has a stack of
8MiB.

Especially on Darwin system, this commit is necessary in order to
prevent a stack overflow, as this system only provides stack sizes of
roughly 500KB which is way to small for a Classic McEliece key.

Fixes #118
2023-09-22 16:30:00 +02:00
Emil Engler
d2d72143b5 Merge pull request #126 from rosenpass/dev/engler/unsafe
Remove some `unsafe`s
2023-09-18 07:20:04 -10:00
Emil Engler
1135cd7bbb util: Remove unsafe from store_secret 2023-09-14 10:36:53 +02:00
Emil Engler
51f04f749f cli: Remove unsafe from store_secret
This commit removes the `unsafe` block from the `store_secret` function,
as I see no reason why we should have one here.
2023-09-14 10:34:07 +02:00
Emil Engler
37d1326481 Merge pull request #123 from rosenpass/dev/engler/unsafe
cli: Move `StaticKEM::keygen` out of `unsafe`
2023-09-13 18:09:28 +02:00
Emil Engler
d0a84294aa cli: Move StaticKEM::keygen out of unsafe
This commit moves the `StaticKEM::keygen` call out of an `unsafe` call,
because the function is not unsafe.
2023-09-13 16:36:35 +02:00
wucke13
a98f64c17d Merge pull request #119 from rosenpass/dev/engler/clippy
Fix all clippy warnings
2023-09-07 12:25:47 +02:00
Emil Engler
d6a7ebe88f clippy: Allow false positive with redundancies
This commit allows a redundant closure call in the regard of clippy
warnings, as it is a false positive in our case.
2023-09-06 17:40:34 +02:00
Emil Engler
212336728c build: Fix clippy warnings in build.rs
This commit fixes the clippy warnings in `build.rs`, by making use of
the `if let` language feature.
2023-09-06 17:32:26 +02:00
Emil Engler
f48a923dbf refactor: Remove redundant references
This commit removes redundant references, noted by clippy.
2023-09-06 17:31:56 +02:00
Emil Engler
7b5d0f7d66 Merge pull request #117 from rosenpass/dev/engler/rp-ip
doc: Clarify the assumptions about the server
2023-09-06 17:20:27 +02:00
Emil Engler
1e37f89e83 doc: Clarify the assumptions about the server
This commit clarifies the assumptions about the server/responder in the
`rp.1` manual page, by specifying an IP and open UDP ports that the rest
of this tutorial is going to assume.

Reported-by: Robert Clausecker <fuzxxl@gmail.com>

Fixes #116
2023-09-06 14:25:48 +02:00
wucke13
b997238f42 chore: Release rosenpass version 0.2.0 2023-09-05 19:33:50 +02:00
12 changed files with 151 additions and 22 deletions

25
Cargo.lock generated
View File

@@ -925,6 +925,15 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "psm"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.33" version = "1.0.33"
@@ -1017,7 +1026,7 @@ checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
[[package]] [[package]]
name = "rosenpass" name = "rosenpass"
version = "0.2.0-rc.1" version = "0.2.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@@ -1032,6 +1041,7 @@ dependencies = [
"oqs-sys", "oqs-sys",
"paste", "paste",
"serde", "serde",
"stacker",
"static_assertions", "static_assertions",
"test_bin", "test_bin",
"thiserror", "thiserror",
@@ -1178,6 +1188,19 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "stacker"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce"
dependencies = [
"cc",
"cfg-if",
"libc",
"psm",
"winapi",
]
[[package]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "rosenpass" name = "rosenpass"
version = "0.2.0-rc.1" version = "0.2.0"
authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"] authors = ["Karolin Varner <karo@cupdev.net>", "wucke13 <wucke13@gmail.com>"]
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
@@ -36,6 +36,7 @@ anyhow = "1.0.71"
[dev-dependencies] [dev-dependencies]
criterion = "0.4.0" criterion = "0.4.0"
test_bin = "0.4.0" test_bin = "0.4.0"
stacker = "0.1.15"
[features] [features]
default = ["log", "env_logger"] default = ["log", "env_logger"]

View File

@@ -21,13 +21,13 @@ fn generate_man() -> String {
// This function is purposely stupid and redundant // This function is purposely stupid and redundant
let man = render_man("mandoc", "./doc/rosenpass.1"); let man = render_man("mandoc", "./doc/rosenpass.1");
if man.is_ok() { if let Ok(man) = man {
return man.unwrap(); return man;
} }
let man = render_man("groff", "./doc/rosenpass.1"); let man = render_man("groff", "./doc/rosenpass.1");
if man.is_ok() { if let Ok(man) = man {
return man.unwrap(); return man;
} }
// TODO: Link to online manual here // TODO: Link to online manual here

View File

@@ -2,6 +2,7 @@ public_key = "peer-a-public-key"
secret_key = "peer-a-secret-key" secret_key = "peer-a-secret-key"
listen = ["[::]:10001"] listen = ["[::]:10001"]
verbosity = "Quiet" verbosity = "Quiet"
control_socket = "rosenpassd.sock"
[[peers]] [[peers]]
public_key = "peer-b-public-key" public_key = "peer-b-public-key"

View File

@@ -59,6 +59,10 @@ listening on the provided IP and port combination, allowing connections from
.Sh EXIT STATUS .Sh EXIT STATUS
.Ex -std .Ex -std
.Sh EXAMPLES .Sh EXAMPLES
In this example, we will assume that the server has an interface bound to
192.168.0.1, that accepts incoming connections on port 9999/UDP for Rosenpass
and port 10000/UDP for WireGuard.
.Pp
To create a VPN connection, start by generating secret keys on both hosts. To create a VPN connection, start by generating secret keys on both hosts.
.Bd -literal -offset indent .Bd -literal -offset indent
rp genkey server.rosenpass-secret rp genkey server.rosenpass-secret

View File

@@ -1,6 +1,8 @@
use anyhow::bail; use anyhow::bail;
use anyhow::Result; use anyhow::Result;
use log::debug;
use log::trace;
use log::{error, info, warn}; use log::{error, info, warn};
use mio::Interest; use mio::Interest;
use mio::Token; use mio::Token;
@@ -15,6 +17,7 @@ use std::net::SocketAddr;
use std::net::SocketAddrV4; use std::net::SocketAddrV4;
use std::net::SocketAddrV6; use std::net::SocketAddrV6;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::process::Stdio; use std::process::Stdio;
@@ -30,6 +33,7 @@ use crate::{
const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0); const IPV4_ANY_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); const IPV6_ANY_ADDR: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0);
const CONTROL_SOCKET_TOKEN: mio::Token = mio::Token(usize::MAX);
fn ipv4_any_binding() -> SocketAddr { fn ipv4_any_binding() -> SocketAddr {
// addr, port // addr, port
@@ -78,6 +82,9 @@ pub struct AppServer {
pub peers: Vec<AppPeer>, pub peers: Vec<AppPeer>,
pub verbosity: Verbosity, pub verbosity: Verbosity,
pub all_sockets_drained: bool, pub all_sockets_drained: bool,
/// Optional control socket to change the configuration of a running rosenpassd
pub maybe_control_socket: Option<mio::net::UnixDatagram>,
} }
/// A socket pointer is an index assigned to a socket; /// A socket pointer is an index assigned to a socket;
@@ -99,7 +106,7 @@ impl SocketPtr {
} }
pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> anyhow::Result<()> { pub fn send_to(&self, srv: &AppServer, buf: &[u8], addr: SocketAddr) -> anyhow::Result<()> {
self.get(srv).send_to(&buf, addr)?; self.get(srv).send_to(buf, addr)?;
Ok(()) Ok(())
} }
} }
@@ -294,13 +301,13 @@ impl HostPathDiscoveryEndpoint {
pub fn send_scouting(&self, srv: &AppServer, buf: &[u8]) -> anyhow::Result<()> { pub fn send_scouting(&self, srv: &AppServer, buf: &[u8]) -> anyhow::Result<()> {
let (addr_off, sock_off) = self.scouting_state.get(); let (addr_off, sock_off) = self.scouting_state.get();
let mut addrs = (&self.addresses) let mut addrs = (self.addresses)
.iter() .iter()
.enumerate() .enumerate()
.cycle() .cycle()
.skip(addr_off) .skip(addr_off)
.take(self.addresses.len()); .take(self.addresses.len());
let mut sockets = (&srv.sockets) let mut sockets = (srv.sockets)
.iter() .iter()
.enumerate() .enumerate()
.cycle() .cycle()
@@ -335,11 +342,13 @@ impl HostPathDiscoveryEndpoint {
} }
impl AppServer { impl AppServer {
pub fn new( pub fn new<P: AsRef<Path> + core::fmt::Debug>(
// TODO @wucke13 check if requiring Debug breaks important types that otherwise fulfill AsRef<Path>
sk: SSk, sk: SSk,
pk: SPk, pk: SPk,
addrs: Vec<SocketAddr>, addrs: Vec<SocketAddr>,
verbosity: Verbosity, verbosity: Verbosity,
uds: Option<P>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
// setup mio // setup mio
let mio_poll = mio::Poll::new()?; let mio_poll = mio::Poll::new()?;
@@ -417,13 +426,31 @@ impl AppServer {
} }
// register all sockets to mio // register all sockets to mio
debug!("registering all UDP sockets to mio");
for (i, socket) in sockets.iter_mut().enumerate() { for (i, socket) in sockets.iter_mut().enumerate() {
trace!("registering {socket:?}");
mio_poll mio_poll
.registry() .registry()
.register(socket, Token(i), Interest::READABLE)?; .register(socket, Token(i), Interest::READABLE)?;
} }
let mut maybe_control_socket = uds
.map(|p| {
debug!("binding control socket {p:?}");
mio::net::UnixDatagram::bind(p)
})
.transpose()?;
if let Some(control_socket) = &mut maybe_control_socket {
debug!("registering control socket to mio");
mio_poll.registry().register(
control_socket,
CONTROL_SOCKET_TOKEN,
Interest::READABLE,
)?;
}
// TODO use mio::net::UnixStream together with std::os::unix::net::UnixStream for Linux // TODO use mio::net::UnixStream together with std::os::unix::net::UnixStream for Linux
debug!("finalizing AppServer creation");
Ok(Self { Ok(Self {
crypt: CryptoServer::new(sk, pk), crypt: CryptoServer::new(sk, pk),
@@ -433,6 +460,7 @@ impl AppServer {
events, events,
mio_poll, mio_poll,
all_sockets_drained: false, all_sockets_drained: false,
maybe_control_socket,
}) })
} }
@@ -524,9 +552,11 @@ impl AppServer {
use AppPollResult::*; use AppPollResult::*;
use KeyOutputReason::*; use KeyOutputReason::*;
match self.poll(&mut *rx)? { match self.poll(&mut *rx)? {
#[allow(clippy::redundant_closure_call)]
SendInitiation(peer) => tx_maybe_with!(peer, || self SendInitiation(peer) => tx_maybe_with!(peer, || self
.crypt .crypt
.initiate_handshake(peer.lower(), &mut *tx))?, .initiate_handshake(peer.lower(), &mut *tx))?,
#[allow(clippy::redundant_closure_call)]
SendRetransmission(peer) => tx_maybe_with!(peer, || self SendRetransmission(peer) => tx_maybe_with!(peer, || self
.crypt .crypt
.retransmit_handshake(peer.lower(), &mut *tx))?, .retransmit_handshake(peer.lower(), &mut *tx))?,
@@ -636,6 +666,7 @@ impl AppServer {
Ok(()) Ok(())
} }
// Polls the crypto servers state machine for new actions
pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> { pub fn poll(&mut self, rx_buf: &mut [u8]) -> anyhow::Result<AppPollResult> {
use crate::protocol::PollResult as C; use crate::protocol::PollResult as C;
use AppPollResult as A; use AppPollResult as A;
@@ -652,7 +683,7 @@ impl AppServer {
} }
} }
/// Tries to receive a new message /// Tries to receive a new control socket command or incoming message
/// ///
/// - might wait for an duration up to `timeout` /// - might wait for an duration up to `timeout`
/// - returns immediately if an error occurs /// - returns immediately if an error occurs
@@ -691,6 +722,27 @@ impl AppServer {
self.mio_poll.poll(&mut self.events, Some(timeout))?; self.mio_poll.poll(&mut self.events, Some(timeout))?;
} }
trace!("checking for new command on control socket");
// control socket always has priority
if let Some(control_socket) = &mut self.maybe_control_socket {
let mut buf = [0u8; 16];
match control_socket.recv(&mut buf) {
Ok(size) => {
// TODO handle command
// to send something here, use the following shell snippet:
//
// printf '\x7\' | nc -NuU rosenpassd.sock
log::debug!("buf received {:?}", &buf[0..size]);
}
Err(e) if e.kind() == ErrorKind::WouldBlock => {
trace!("no new commands on control socket")
}
Err(e) => return Err(e.into()),
}
}
// then normal traffic is processed
let mut would_block_count = 0; let mut would_block_count = 0;
for (sock_no, socket) in self.sockets.iter_mut().enumerate() { for (sock_no, socket) in self.sockets.iter_mut().enumerate() {
match socket.recv_from(buf) { match socket.recv_from(buf) {

View File

@@ -164,12 +164,10 @@ impl Cli {
// generate the keys and store them in files // generate the keys and store them in files
let mut ssk = crate::protocol::SSk::random(); let mut ssk = crate::protocol::SSk::random();
let mut spk = crate::protocol::SPk::random(); let mut spk = crate::protocol::SPk::random();
StaticKEM::keygen(ssk.secret_mut(), spk.secret_mut())?;
unsafe { ssk.store_secret(skf)?;
StaticKEM::keygen(ssk.secret_mut(), spk.secret_mut())?; spk.store_secret(pkf)?;
ssk.store_secret(skf)?;
spk.store_secret(pkf)?;
}
} }
ExchangeConfig { config_file } => { ExchangeConfig { config_file } => {
@@ -230,6 +228,7 @@ impl Cli {
pk, pk,
config.listen, config.listen,
config.verbosity, config.verbosity,
config.control_socket.as_ref(),
)?); )?);
for cfg_peer in config.peers { for cfg_peer in config.peers {
@@ -252,11 +251,11 @@ impl Cli {
} }
trait StoreSecret { trait StoreSecret {
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()>; fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()>;
} }
impl<const N: usize> StoreSecret for Secret<N> { impl<const N: usize> StoreSecret for Secret<N> {
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> { fn store_secret<P: AsRef<Path>>(&self, path: P) -> anyhow::Result<()> {
std::fs::write(path, self.secret())?; std::fs::write(path, self.secret())?;
Ok(()) Ok(())
} }

View File

@@ -25,6 +25,8 @@ pub struct Rosenpass {
#[serde(skip)] #[serde(skip)]
pub config_file_path: PathBuf, pub config_file_path: PathBuf,
pub control_socket: Option<PathBuf>,
} }
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -133,6 +135,7 @@ impl Rosenpass {
verbosity: Verbosity::Quiet, verbosity: Verbosity::Quiet,
peers: vec![], peers: vec![],
config_file_path: PathBuf::new(), config_file_path: PathBuf::new(),
control_socket: None,
} }
} }

38
src/control_commands.rs Normal file
View File

@@ -0,0 +1,38 @@
//! Data structures representing the control messages going over the control socket
//!
//! This module uses the same de-/serialization mechanism as [crate::msgs].
//! If you want to interface with `rosenpassd`, this is where you can look up the format
//! of the messages that are accepted.
use crate::{data_lense, msgs::LenseView, RosenpassError};
data_lense! { ControlComand<C> :=
/// [MsgType] of this message
msg_type: 1
}
#[repr(u8)]
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum CommandType {
/// Add one peer
AddPeer = 0x10,
/// Remove all peers that match the given public key
RemovePeerPk = 0x11,
/// Remove all peers that match the given address
RemovePeerIp = 0x12,
}
impl TryFrom<u8> for CommandType {
type Error = RosenpassError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
0x10 => CommandType::AddPeer,
0x11 => CommandType::RemovePeerPk,
0x12 => CommandType::RemovePeerIp,
_ => return Err(RosenpassError::InvalidMessageType(value)),
})
}
}

View File

@@ -8,6 +8,7 @@ pub mod labeled_prf;
pub mod app_server; pub mod app_server;
pub mod cli; pub mod cli;
pub mod config; pub mod config;
pub mod control_commands;
pub mod msgs; pub mod msgs;
pub mod pqkem; pub mod pqkem;
pub mod prftree; pub mod prftree;
@@ -26,6 +27,9 @@ pub enum RosenpassError {
}, },
#[error("invalid message type")] #[error("invalid message type")]
InvalidMessageType(u8), InvalidMessageType(u8),
#[error("invalid command type")]
InvalidCommandType(u8),
} }
impl RosenpassError { impl RosenpassError {

View File

@@ -1739,7 +1739,11 @@ mod test {
// initialize secret and public key for the crypto server // initialize secret and public key for the crypto server
let (mut sk, mut pk) = (SSk::zero(), SPk::zero()); let (mut sk, mut pk) = (SSk::zero(), SPk::zero());
StaticKEM::keygen(sk.secret_mut(), pk.secret_mut()).expect("unable to generate keys");
// Guranteed to have 16MB of stack size
stacker::grow(8 * 1024 * 1024, || {
StaticKEM::keygen(sk.secret_mut(), pk.secret_mut()).expect("unable to generate keys");
});
CryptoServer::new(sk, pk) CryptoServer::new(sk, pk)
} }

View File

@@ -172,11 +172,11 @@ trait StoreValue {
} }
trait StoreSecret { trait StoreSecret {
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()>; fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()>;
} }
impl<T: StoreValue> StoreSecret for T { impl<T: StoreValue> StoreSecret for T {
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> { fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
self.store(path) self.store(path)
} }
} }
@@ -211,7 +211,7 @@ impl<const N: usize> LoadValueB64 for Secret<N> {
} }
impl<const N: usize> StoreSecret for Secret<N> { impl<const N: usize> StoreSecret for Secret<N> {
unsafe fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> { fn store_secret<P: AsRef<Path>>(&self, path: P) -> Result<()> {
std::fs::write(path, self.secret())?; std::fs::write(path, self.secret())?;
Ok(()) Ok(())
} }