Cleanup & incomplete tests

Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
Luca Fulchir 2023-06-10 14:42:24 +02:00
parent faaf8762c7
commit aff1c313f5
Signed by: luca.fulchir
GPG Key ID: 8F6440603D13A78E
14 changed files with 618 additions and 414 deletions

View File

@ -18,6 +18,7 @@
pkgs-unstable = import nixpkgs-unstable { pkgs-unstable = import nixpkgs-unstable {
inherit system overlays; inherit system overlays;
}; };
RUST_VERSION="1.69.0";
in in
{ {
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
@ -40,7 +41,7 @@
cargo-flamegraph cargo-flamegraph
cargo-license cargo-license
lld lld
rust-bin.stable."1.69.0".default rust-bin.stable.${RUST_VERSION}.default
rustfmt rustfmt
rust-analyzer rust-analyzer
# fenrir deps # fenrir deps

View File

@ -3,7 +3,11 @@
use crate::{ use crate::{
connection::handshake::HandshakeID, connection::handshake::HandshakeID,
enc::{asym::KeyExchangeKind, hkdf::HkdfKind, sym::CipherKind}, enc::{
asym::{KeyExchangeKind, KeyID, PrivKey, PubKey},
hkdf::HkdfKind,
sym::CipherKind,
},
}; };
use ::std::{ use ::std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
@ -30,6 +34,8 @@ pub struct Config {
pub hkdfs: Vec<HkdfKind>, pub hkdfs: Vec<HkdfKind>,
/// Supported Ciphers /// Supported Ciphers
pub ciphers: Vec<CipherKind>, pub ciphers: Vec<CipherKind>,
/// list of public/private keys
pub keys: Vec<(KeyID, PrivKey, PubKey)>,
} }
impl Default for Config { impl Default for Config {
@ -50,6 +56,7 @@ impl Default for Config {
key_exchanges: [KeyExchangeKind::X25519DiffieHellman].to_vec(), key_exchanges: [KeyExchangeKind::X25519DiffieHellman].to_vec(),
hkdfs: [HkdfKind::Sha3].to_vec(), hkdfs: [HkdfKind::Sha3].to_vec(),
ciphers: [CipherKind::XChaCha20Poly1305].to_vec(), ciphers: [CipherKind::XChaCha20Poly1305].to_vec(),
keys: Vec::new(),
} }
} }
} }

View File

@ -3,14 +3,11 @@
pub mod dirsync; pub mod dirsync;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub(crate) mod tracker;
use crate::{ use crate::{
auth::ServiceID, connection::ProtocolVersion,
connection::{self, Connection, IDRecv, ProtocolVersion}, enc::sym::{HeadLen, TagLen},
enc::{
asym::{KeyID, PrivKey, PubKey},
sym::{HeadLen, TagLen},
},
}; };
use ::num_traits::FromPrimitive; use ::num_traits::FromPrimitive;
@ -70,106 +67,6 @@ impl HandshakeID {
1 1
} }
} }
pub(crate) struct HandshakeServer {
pub id: KeyID,
pub key: PrivKey,
}
pub(crate) struct HandshakeClient {
pub service_id: ServiceID,
pub service_conn_id: IDRecv,
pub connection: Connection,
pub timeout: Option<::tokio::task::JoinHandle<()>>,
}
/// Tracks the keys used by the client and the handshake
/// they are associated with
pub(crate) struct HandshakeClientList {
used: Vec<::bitmaps::Bitmap<1024>>, // index = KeyID
keys: Vec<Option<(PrivKey, PubKey)>>,
list: Vec<Option<HandshakeClient>>,
}
impl HandshakeClientList {
pub(crate) fn new() -> Self {
Self {
used: [::bitmaps::Bitmap::<1024>::new()].to_vec(),
keys: Vec::with_capacity(16),
list: Vec::with_capacity(16),
}
}
pub(crate) fn get(&self, id: KeyID) -> Option<&HandshakeClient> {
if id.0 as usize >= self.list.len() {
return None;
}
self.list[id.0 as usize].as_ref()
}
pub(crate) fn remove(&mut self, id: KeyID) -> Option<HandshakeClient> {
if id.0 as usize >= self.list.len() {
return None;
}
let used_vec_idx = id.0 as usize / 1024;
let used_bitmap_idx = id.0 as usize % 1024;
let used_iter = match self.used.get_mut(used_vec_idx) {
Some(used_iter) => used_iter,
None => return None,
};
used_iter.set(used_bitmap_idx, false);
self.keys[id.0 as usize] = None;
let mut owned = None;
::core::mem::swap(&mut self.list[id.0 as usize], &mut owned);
owned
}
pub(crate) fn add(
&mut self,
priv_key: PrivKey,
pub_key: PubKey,
service_id: ServiceID,
service_conn_id: IDRecv,
connection: Connection,
) -> Result<(KeyID, &mut HandshakeClient), ()> {
let maybe_free_key_idx =
self.used.iter().enumerate().find_map(|(idx, bmap)| {
match bmap.first_false_index() {
Some(false_idx) => Some(((idx * 1024), false_idx)),
None => None,
}
});
let free_key_idx = match maybe_free_key_idx {
Some((idx, false_idx)) => {
let free_key_idx = idx * 1024 + false_idx;
if free_key_idx > KeyID::MAX as usize {
return Err(());
}
self.used[idx].set(false_idx, true);
free_key_idx
}
None => {
let mut bmap = ::bitmaps::Bitmap::<1024>::new();
bmap.set(0, true);
self.used.push(bmap);
self.used.len() * 1024
}
};
if self.keys.len() >= free_key_idx {
self.keys.push(None);
self.list.push(None);
}
self.keys[free_key_idx] = Some((priv_key, pub_key));
self.list[free_key_idx] = Some(HandshakeClient {
service_id,
service_conn_id,
connection,
timeout: None,
});
Ok((
KeyID(free_key_idx as u16),
self.list[free_key_idx].as_mut().unwrap(),
))
}
}
/// Parsed handshake /// Parsed handshake
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum HandshakeData { pub enum HandshakeData {

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
auth, auth,
connection::{handshake::*, ID}, connection::{handshake::*, ID},
enc, enc::{self, asym::KeyID},
}; };
#[test] #[test]

View File

@ -0,0 +1,326 @@
//! Handhsake handling
use crate::{
auth::ServiceID,
connection::{
self,
handshake::{self, Error, Handshake},
Connection, IDRecv,
},
enc::{
self,
asym::{self, KeyID, PrivKey, PubKey},
hkdf::{Hkdf, HkdfKind},
sym::{CipherKind, CipherRecv},
},
inner::ThreadTracker,
};
pub(crate) struct HandshakeServer {
pub id: KeyID,
pub key: PrivKey,
}
pub(crate) struct HandshakeClient {
pub service_id: ServiceID,
pub service_conn_id: IDRecv,
pub connection: Connection,
pub timeout: Option<::tokio::task::JoinHandle<()>>,
}
/// Tracks the keys used by the client and the handshake
/// they are associated with
pub(crate) struct HandshakeClientList {
used: Vec<::bitmaps::Bitmap<1024>>, // index = KeyID
keys: Vec<Option<(PrivKey, PubKey)>>,
list: Vec<Option<HandshakeClient>>,
}
impl HandshakeClientList {
pub(crate) fn new() -> Self {
Self {
used: [::bitmaps::Bitmap::<1024>::new()].to_vec(),
keys: Vec::with_capacity(16),
list: Vec::with_capacity(16),
}
}
pub(crate) fn get(&self, id: KeyID) -> Option<&HandshakeClient> {
if id.0 as usize >= self.list.len() {
return None;
}
self.list[id.0 as usize].as_ref()
}
pub(crate) fn remove(&mut self, id: KeyID) -> Option<HandshakeClient> {
if id.0 as usize >= self.list.len() {
return None;
}
let used_vec_idx = id.0 as usize / 1024;
let used_bitmap_idx = id.0 as usize % 1024;
let used_iter = match self.used.get_mut(used_vec_idx) {
Some(used_iter) => used_iter,
None => return None,
};
used_iter.set(used_bitmap_idx, false);
self.keys[id.0 as usize] = None;
let mut owned = None;
::core::mem::swap(&mut self.list[id.0 as usize], &mut owned);
owned
}
pub(crate) fn add(
&mut self,
priv_key: PrivKey,
pub_key: PubKey,
service_id: ServiceID,
service_conn_id: IDRecv,
connection: Connection,
) -> Result<(KeyID, &mut HandshakeClient), ()> {
let maybe_free_key_idx =
self.used.iter().enumerate().find_map(|(idx, bmap)| {
match bmap.first_false_index() {
Some(false_idx) => Some(((idx * 1024), false_idx)),
None => None,
}
});
let free_key_idx = match maybe_free_key_idx {
Some((idx, false_idx)) => {
let free_key_idx = idx * 1024 + false_idx;
if free_key_idx > KeyID::MAX as usize {
return Err(());
}
self.used[idx].set(false_idx, true);
free_key_idx
}
None => {
let mut bmap = ::bitmaps::Bitmap::<1024>::new();
bmap.set(0, true);
self.used.push(bmap);
self.used.len() * 1024
}
};
if self.keys.len() >= free_key_idx {
self.keys.push(None);
self.list.push(None);
}
self.keys[free_key_idx] = Some((priv_key, pub_key));
self.list[free_key_idx] = Some(HandshakeClient {
service_id,
service_conn_id,
connection,
timeout: None,
});
Ok((
KeyID(free_key_idx as u16),
self.list[free_key_idx].as_mut().unwrap(),
))
}
}
/// Information needed to reply after the key exchange
#[derive(Debug, Clone)]
pub(crate) struct AuthNeededInfo {
/// Parsed handshake packet
pub handshake: Handshake,
/// hkdf generated from the handshake
pub hkdf: Hkdf,
/// cipher to be used in both directions
pub cipher: CipherKind,
}
/// Client information needed to fully establish the conenction
#[derive(Debug)]
pub(crate) struct ClientConnectInfo {
/// The service ID that we are connecting to
pub service_id: ServiceID,
/// The service ID that we are connecting to
pub service_connection_id: IDRecv,
/// Parsed handshake packet
pub handshake: Handshake,
/// Connection
pub connection: Connection,
}
/// Intermediate actions to be taken while parsing the handshake
#[derive(Debug)]
pub(crate) enum HandshakeAction {
/// Parsing finished, all ok, nothing to do
Nonthing,
/// Packet parsed, now go perform authentication
AuthNeeded(AuthNeededInfo),
/// the client can fully establish a connection with this info
ClientConnect(ClientConnectInfo),
}
/// Tracking of handhsakes and conenctions
/// Note that we have multiple Handshake trackers, pinned to different cores
/// Each of them will handle a subset of all handshakes.
/// Each handshake is routed to a different tracker by checking
/// core = (udp_src_sender_port % total_threads) - 1
pub(crate) struct HandshakeTracker {
thread_id: ThreadTracker,
key_exchanges: Vec<(asym::KeyKind, asym::KeyExchangeKind)>,
ciphers: Vec<CipherKind>,
/// ephemeral keys used server side in key exchange
keys_srv: Vec<HandshakeServer>,
/// ephemeral keys used client side in key exchange
hshake_cli: HandshakeClientList,
}
impl HandshakeTracker {
pub(crate) fn new(thread_id: ThreadTracker) -> Self {
Self {
thread_id,
ciphers: Vec::new(),
key_exchanges: Vec::new(),
keys_srv: Vec::new(),
hshake_cli: HandshakeClientList::new(),
}
}
pub(crate) fn new_client(
&mut self,
priv_key: PrivKey,
pub_key: PubKey,
service_id: ServiceID,
service_conn_id: IDRecv,
connection: Connection,
) -> Result<(KeyID, &mut HandshakeClient), ()> {
self.hshake_cli.add(
priv_key,
pub_key,
service_id,
service_conn_id,
connection,
)
}
pub(crate) fn timeout_client(
&mut self,
key_id: KeyID,
) -> Option<[IDRecv; 2]> {
if let Some(hshake) = self.hshake_cli.remove(key_id) {
Some([hshake.connection.id_recv, hshake.service_conn_id])
} else {
None
}
}
pub(crate) fn recv_handshake(
&mut self,
mut handshake: Handshake,
handshake_raw: &mut [u8],
) -> Result<HandshakeAction, Error> {
use connection::handshake::{dirsync::DirSync, HandshakeData};
match handshake.data {
HandshakeData::DirSync(ref mut ds) => match ds {
DirSync::Req(ref mut req) => {
let ephemeral_key = {
if let Some(h_k) =
self.keys_srv.iter().find(|k| k.id == req.key_id)
{
// Directory synchronized can only use keys
// for key exchange, not signing keys
if let PrivKey::Exchange(k) = &h_k.key {
Some(k.clone())
} else {
None
}
} else {
None
}
};
if ephemeral_key.is_none() {
::tracing::debug!(
"No such server key id: {:?}",
req.key_id
);
return Err(handshake::Error::UnknownKeyID.into());
}
let ephemeral_key = ephemeral_key.unwrap();
{
if None
== self.key_exchanges.iter().find(|&x| {
*x == (ephemeral_key.kind(), req.exchange)
})
{
return Err(
enc::Error::UnsupportedKeyExchange.into()
);
}
}
{
if None
== self.ciphers.iter().find(|&x| *x == req.cipher)
{
return Err(enc::Error::UnsupportedCipher.into());
}
}
let shared_key = match ephemeral_key
.key_exchange(req.exchange, req.exchange_key)
{
Ok(shared_key) => shared_key,
Err(e) => return Err(handshake::Error::Key(e).into()),
};
let hkdf = Hkdf::new(HkdfKind::Sha3, b"fenrir", shared_key);
let secret_recv = hkdf.get_secret(b"to_server");
let cipher_recv = CipherRecv::new(req.cipher, secret_recv);
use crate::enc::sym::AAD;
let aad = AAD(&mut []); // no aad for now
match cipher_recv.decrypt(
aad,
&mut handshake_raw[req.encrypted_offset()..],
) {
Ok(cleartext) => {
req.data.deserialize_as_cleartext(cleartext)?;
}
Err(e) => {
return Err(handshake::Error::Key(e).into());
}
}
let cipher = req.cipher;
return Ok(HandshakeAction::AuthNeeded(AuthNeededInfo {
handshake,
hkdf,
cipher,
}));
}
DirSync::Resp(resp) => {
let hshake = match self.hshake_cli.get(resp.client_key_id) {
Some(hshake) => hshake,
None => {
::tracing::debug!(
"No such client key id: {:?}",
resp.client_key_id
);
return Err(handshake::Error::UnknownKeyID.into());
}
};
let cipher_recv = &hshake.connection.cipher_recv;
use crate::enc::sym::AAD;
// no aad for now
let aad = AAD(&mut []);
let mut raw_data = &mut handshake_raw[resp
.encrypted_offset()
..(resp.encrypted_offset() + resp.encrypted_length())];
match cipher_recv.decrypt(aad, &mut raw_data) {
Ok(cleartext) => {
resp.data.deserialize_as_cleartext(&cleartext)?;
}
Err(e) => {
return Err(handshake::Error::Key(e).into());
}
}
let hshake =
self.hshake_cli.remove(resp.client_key_id).unwrap();
if let Some(timeout) = hshake.timeout {
timeout.abort();
}
return Ok(HandshakeAction::ClientConnect(
ClientConnectInfo {
service_id: hshake.service_id,
service_connection_id: hshake.service_conn_id,
handshake,
connection: hshake.connection,
},
));
}
},
}
}
}

View File

@ -9,6 +9,9 @@ pub use record::Record;
use crate::auth::Domain; use crate::auth::Domain;
#[cfg(test)]
mod tests;
/// Common errors for Dnssec setup and usage /// Common errors for Dnssec setup and usage
#[derive(::thiserror::Error, Debug)] #[derive(::thiserror::Error, Debug)]
pub enum Error { pub enum Error {
@ -44,7 +47,7 @@ pub struct Dnssec {
impl Dnssec { impl Dnssec {
/// Spawn connections to DNS via TCP /// Spawn connections to DNS via TCP
pub async fn new(resolvers: &Vec<SocketAddr>) -> Result<Self, Error> { pub fn new(resolvers: &Vec<SocketAddr>) -> Result<Self, Error> {
// use a TCP connection to the DNS. // use a TCP connection to the DNS.
// the records we need are big, will not fit in a UDP packet // the records we need are big, will not fit in a UDP packet
let resolv_conf_resolvers: Vec<SocketAddr>; let resolv_conf_resolvers: Vec<SocketAddr>;
@ -146,62 +149,3 @@ impl Dnssec {
}; };
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serialization() {
let rand = enc::Random::new();
let (_, exchange_key) =
match enc::asym::KeyExchangeKind::X25519DiffieHellman
.new_keypair(&rand)
{
Ok(pair) => pair,
Err(_) => {
assert!(false, "Can't generate random keypair");
return;
}
};
use crate::enc;
let record = Record {
public_keys : [(enc::asym::KeyID(42),
enc::asym::PubKey::Exchange(exchange_key))].to_vec(),
addresses: [record::Address {
ip: ::std::net::IpAddr::V4(::std::net::Ipv4Addr::new(127,0,0,1)),
port: Some(::core::num::NonZeroU16::new(31337).unwrap()),
priority: record::AddressPriority::P1,
weight: record::AddressWeight::W1,
handshake_ids: [crate::connection::handshake::HandshakeID::DirectorySynchronized].to_vec(),
public_key_idx : [record::PubKeyIdx(0)].to_vec(),
}].to_vec(),
key_exchanges: [enc::asym::KeyExchangeKind::X25519DiffieHellman].to_vec(),
hkdfs: [enc::hkdf::HkdfKind::Sha3].to_vec(),
ciphers: [enc::sym::CipherKind::XChaCha20Poly1305].to_vec(),
};
let encoded = match record.encode() {
Ok(encoded) => encoded,
Err(e) => {
assert!(false, "{}", e.to_string());
return;
}
};
let full_record = "v=Fenrir1 ".to_string() + &encoded;
let record = match Dnssec::parse_txt_record(&full_record) {
Ok(record) => record,
Err(e) => {
assert!(false, "{}", e.to_string());
return;
}
};
let _re_encoded = match record.encode() {
Ok(re_encoded) => re_encoded,
Err(e) => {
assert!(false, "{}", e.to_string());
return;
}
};
}
}

View File

@ -46,7 +46,7 @@ use crate::{
connection::handshake::HandshakeID, connection::handshake::HandshakeID,
enc::{ enc::{
self, self,
asym::{ExchangePubKey, KeyExchangeKind, KeyID, PubKey}, asym::{KeyExchangeKind, KeyID, PubKey},
hkdf::HkdfKind, hkdf::HkdfKind,
sym::CipherKind, sym::CipherKind,
}, },

59
src/dnssec/tests.rs Normal file
View File

@ -0,0 +1,59 @@
use super::*;
#[test]
fn test_dnssec_serialization() {
let rand = enc::Random::new();
let (_, exchange_key) =
match enc::asym::KeyExchangeKind::X25519DiffieHellman.new_keypair(&rand)
{
Ok(pair) => pair,
Err(_) => {
assert!(false, "Can't generate random keypair");
return;
}
};
use crate::{connection::handshake::HandshakeID, enc};
let record = Record {
public_keys: [(
enc::asym::KeyID(42),
enc::asym::PubKey::Exchange(exchange_key),
)]
.to_vec(),
addresses: [record::Address {
ip: ::std::net::IpAddr::V4(::std::net::Ipv4Addr::new(127, 0, 0, 1)),
port: Some(::core::num::NonZeroU16::new(31337).unwrap()),
priority: record::AddressPriority::P1,
weight: record::AddressWeight::W1,
handshake_ids: [HandshakeID::DirectorySynchronized].to_vec(),
public_key_idx: [record::PubKeyIdx(0)].to_vec(),
}]
.to_vec(),
key_exchanges: [enc::asym::KeyExchangeKind::X25519DiffieHellman]
.to_vec(),
hkdfs: [enc::hkdf::HkdfKind::Sha3].to_vec(),
ciphers: [enc::sym::CipherKind::XChaCha20Poly1305].to_vec(),
};
let encoded = match record.encode() {
Ok(encoded) => encoded,
Err(e) => {
assert!(false, "{}", e.to_string());
return;
}
};
let full_record = "v=Fenrir1 ".to_string() + &encoded;
let record = match Dnssec::parse_txt_record(&full_record) {
Ok(record) => record,
Err(e) => {
assert!(false, "{}", e.to_string());
return;
}
};
let _re_encoded = match record.encode() {
Ok(re_encoded) => re_encoded,
Err(e) => {
assert!(false, "{}", e.to_string());
return;
}
};
}

View File

@ -169,7 +169,6 @@ impl KeyExchangeKind {
let priv_key = ExchangePrivKey::X25519(raw_priv); let priv_key = ExchangePrivKey::X25519(raw_priv);
Ok((priv_key, pub_key)) Ok((priv_key, pub_key))
} }
_ => Err(Error::UnsupportedKeyExchange),
} }
} }
} }
@ -300,6 +299,15 @@ impl PrivKey {
} }
} }
} }
// Fake debug implementation to avoid leaking secrets
impl ::core::fmt::Debug for PrivKey {
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> Result<(), ::std::fmt::Error> {
::core::fmt::Debug::fmt("[hidden privkey]", f)
}
}
/// Ephemeral private keys /// Ephemeral private keys
#[derive(Clone)] #[derive(Clone)]

View File

@ -5,7 +5,6 @@ use crate::{
config::Config, config::Config,
enc::{Random, Secret}, enc::{Random, Secret},
}; };
use ::zeroize::Zeroize;
/// List of possible Ciphers /// List of possible Ciphers
#[derive( #[derive(

View File

@ -4,60 +4,6 @@
pub(crate) mod worker; pub(crate) mod worker;
use crate::{
auth::ServiceID,
connection::{
self,
handshake::{
self, Handshake, HandshakeClient, HandshakeClientList,
HandshakeServer,
},
Connection, IDRecv,
},
enc::{
self,
asym::{self, KeyID, PrivKey, PubKey},
hkdf::{Hkdf, HkdfKind},
sym::{CipherKind, CipherRecv},
},
Error,
};
use ::std::vec::Vec;
/// Information needed to reply after the key exchange
#[derive(Debug, Clone)]
pub(crate) struct AuthNeededInfo {
/// Parsed handshake packet
pub handshake: Handshake,
/// hkdf generated from the handshake
pub hkdf: Hkdf,
/// cipher to be used in both directions
pub cipher: CipherKind,
}
/// Client information needed to fully establish the conenction
#[derive(Debug)]
pub(crate) struct ClientConnectInfo {
/// The service ID that we are connecting to
pub service_id: ServiceID,
/// The service ID that we are connecting to
pub service_connection_id: IDRecv,
/// Parsed handshake packet
pub handshake: Handshake,
/// Connection
pub connection: Connection,
}
/// Intermediate actions to be taken while parsing the handshake
#[derive(Debug)]
pub(crate) enum HandshakeAction {
/// Parsing finished, all ok, nothing to do
Nonthing,
/// Packet parsed, now go perform authentication
AuthNeeded(AuthNeededInfo),
/// the client can fully establish a connection with this info
ClientConnect(ClientConnectInfo),
}
/// Track the total number of threads and our index /// Track the total number of threads and our index
/// 65K cpus should be enough for anybody /// 65K cpus should be enough for anybody
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -66,180 +12,3 @@ pub(crate) struct ThreadTracker {
/// Note: starts from 1 /// Note: starts from 1
pub id: u16, pub id: u16,
} }
/// Tracking of handhsakes and conenctions
/// Note that we have multiple Handshake trackers, pinned to different cores
/// Each of them will handle a subset of all handshakes.
/// Each handshake is routed to a different tracker by checking
/// core = (udp_src_sender_port % total_threads) - 1
pub(crate) struct HandshakeTracker {
thread_id: ThreadTracker,
key_exchanges: Vec<(asym::KeyKind, asym::KeyExchangeKind)>,
ciphers: Vec<CipherKind>,
/// ephemeral keys used server side in key exchange
keys_srv: Vec<HandshakeServer>,
/// ephemeral keys used client side in key exchange
hshake_cli: HandshakeClientList,
}
impl HandshakeTracker {
pub(crate) fn new(thread_id: ThreadTracker) -> Self {
Self {
thread_id,
ciphers: Vec::new(),
key_exchanges: Vec::new(),
keys_srv: Vec::new(),
hshake_cli: HandshakeClientList::new(),
}
}
pub(crate) fn new_client(
&mut self,
priv_key: PrivKey,
pub_key: PubKey,
service_id: ServiceID,
service_conn_id: IDRecv,
connection: Connection,
) -> Result<(KeyID, &mut HandshakeClient), ()> {
self.hshake_cli.add(
priv_key,
pub_key,
service_id,
service_conn_id,
connection,
)
}
pub(crate) fn timeout_client(
&mut self,
key_id: KeyID,
) -> Option<[IDRecv; 2]> {
if let Some(hshake) = self.hshake_cli.remove(key_id) {
Some([hshake.connection.id_recv, hshake.service_conn_id])
} else {
None
}
}
pub(crate) fn recv_handshake(
&mut self,
mut handshake: Handshake,
handshake_raw: &mut [u8],
) -> Result<HandshakeAction, Error> {
use connection::handshake::{dirsync::DirSync, HandshakeData};
match handshake.data {
HandshakeData::DirSync(ref mut ds) => match ds {
DirSync::Req(ref mut req) => {
let ephemeral_key = {
if let Some(h_k) =
self.keys_srv.iter().find(|k| k.id == req.key_id)
{
// Directory synchronized can only use keys
// for key exchange, not signing keys
if let PrivKey::Exchange(k) = &h_k.key {
Some(k.clone())
} else {
None
}
} else {
None
}
};
if ephemeral_key.is_none() {
::tracing::debug!(
"No such server key id: {:?}",
req.key_id
);
return Err(handshake::Error::UnknownKeyID.into());
}
let ephemeral_key = ephemeral_key.unwrap();
{
if None
== self.key_exchanges.iter().find(|&x| {
*x == (ephemeral_key.kind(), req.exchange)
})
{
return Err(
enc::Error::UnsupportedKeyExchange.into()
);
}
}
{
if None
== self.ciphers.iter().find(|&x| *x == req.cipher)
{
return Err(enc::Error::UnsupportedCipher.into());
}
}
let shared_key = match ephemeral_key
.key_exchange(req.exchange, req.exchange_key)
{
Ok(shared_key) => shared_key,
Err(e) => return Err(handshake::Error::Key(e).into()),
};
let hkdf = Hkdf::new(HkdfKind::Sha3, b"fenrir", shared_key);
let secret_recv = hkdf.get_secret(b"to_server");
let cipher_recv = CipherRecv::new(req.cipher, secret_recv);
use crate::enc::sym::AAD;
let aad = AAD(&mut []); // no aad for now
match cipher_recv.decrypt(
aad,
&mut handshake_raw[req.encrypted_offset()..],
) {
Ok(cleartext) => {
req.data.deserialize_as_cleartext(cleartext)?;
}
Err(e) => {
return Err(handshake::Error::Key(e).into());
}
}
let cipher = req.cipher;
return Ok(HandshakeAction::AuthNeeded(AuthNeededInfo {
handshake,
hkdf,
cipher,
}));
}
DirSync::Resp(resp) => {
let hshake = match self.hshake_cli.get(resp.client_key_id) {
Some(hshake) => hshake,
None => {
::tracing::debug!(
"No such client key id: {:?}",
resp.client_key_id
);
return Err(handshake::Error::UnknownKeyID.into());
}
};
let cipher_recv = &hshake.connection.cipher_recv;
use crate::enc::sym::AAD;
// no aad for now
let aad = AAD(&mut []);
let mut raw_data = &mut handshake_raw[resp
.encrypted_offset()
..(resp.encrypted_offset() + resp.encrypted_length())];
match cipher_recv.decrypt(aad, &mut raw_data) {
Ok(cleartext) => {
resp.data.deserialize_as_cleartext(&cleartext)?;
}
Err(e) => {
return Err(handshake::Error::Key(e).into());
}
}
let hshake =
self.hshake_cli.remove(resp.client_key_id).unwrap();
if let Some(timeout) = hshake.timeout {
timeout.abort();
}
return Ok(HandshakeAction::ClientConnect(
ClientConnectInfo {
service_id: hshake.service_id,
service_connection_id: hshake.service_conn_id,
handshake,
connection: hshake.connection,
},
));
}
},
}
}
}

View File

@ -7,6 +7,7 @@ use crate::{
handshake::{ handshake::{
self, self,
dirsync::{self, DirSync}, dirsync::{self, DirSync},
tracker::{HandshakeAction, HandshakeTracker},
Handshake, HandshakeData, Handshake, HandshakeData,
}, },
socket::{UdpClient, UdpServer}, socket::{UdpClient, UdpServer},
@ -18,9 +19,9 @@ use crate::{
hkdf::{self, Hkdf, HkdfKind}, hkdf::{self, Hkdf, HkdfKind},
sym, Random, Secret, sym, Random, Secret,
}, },
inner::{HandshakeAction, HandshakeTracker, ThreadTracker}, inner::ThreadTracker,
}; };
use ::std::{rc::Rc, sync::Arc, vec::Vec}; use ::std::{sync::Arc, vec::Vec};
/// This worker must be cpu-pinned /// This worker must be cpu-pinned
use ::tokio::{ use ::tokio::{
net::UdpSocket, net::UdpSocket,

View File

@ -20,6 +20,9 @@ pub mod dnssec;
pub mod enc; pub mod enc;
mod inner; mod inner;
#[cfg(test)]
mod tests;
use ::std::{sync::Arc, vec::Vec}; use ::std::{sync::Arc, vec::Vec};
use ::tokio::{net::UdpSocket, sync::Mutex}; use ::tokio::{net::UdpSocket, sync::Mutex};
@ -74,7 +77,7 @@ pub struct Fenrir {
/// listening udp sockets /// listening udp sockets
sockets: SocketList, sockets: SocketList,
/// DNSSEC resolver, with failovers /// DNSSEC resolver, with failovers
dnssec: Option<dnssec::Dnssec>, dnssec: dnssec::Dnssec,
/// Broadcast channel to tell workers to stop working /// Broadcast channel to tell workers to stop working
stop_working: ::tokio::sync::broadcast::Sender<bool>, stop_working: ::tokio::sync::broadcast::Sender<bool>,
/// where to ask for token check /// where to ask for token check
@ -100,10 +103,11 @@ impl Fenrir {
/// Create a new Fenrir endpoint /// Create a new Fenrir endpoint
pub fn new(config: &Config) -> Result<Self, Error> { pub fn new(config: &Config) -> Result<Self, Error> {
let (sender, _) = ::tokio::sync::broadcast::channel(1); let (sender, _) = ::tokio::sync::broadcast::channel(1);
let dnssec = dnssec::Dnssec::new(&config.resolvers)?;
let endpoint = Fenrir { let endpoint = Fenrir {
cfg: config.clone(), cfg: config.clone(),
sockets: SocketList::new(), sockets: SocketList::new(),
dnssec: None, dnssec,
stop_working: sender, stop_working: sender,
token_check: None, token_check: None,
conn_auth_srv: Mutex::new(AuthServerConnections::new()), conn_auth_srv: Mutex::new(AuthServerConnections::new()),
@ -113,6 +117,7 @@ impl Fenrir {
Ok(endpoint) Ok(endpoint)
} }
///FIXME: remove this, move into new()
/// Start all workers, listeners /// Start all workers, listeners
pub async fn start( pub async fn start(
&mut self, &mut self,
@ -123,7 +128,14 @@ impl Fenrir {
self.stop().await; self.stop().await;
return Err(e.into()); return Err(e.into());
} }
self.dnssec = Some(dnssec::Dnssec::new(&self.cfg.resolvers).await?); Ok(())
}
///FIXME: remove this, move into new()
pub async fn setup_no_workers(&mut self) -> Result<(), Error> {
if let Err(e) = self.add_sockets().await {
self.stop().await;
return Err(e.into());
}
Ok(()) Ok(())
} }
@ -137,7 +149,6 @@ impl Fenrir {
let mut old_thread_pool = Vec::new(); let mut old_thread_pool = Vec::new();
::std::mem::swap(&mut self._thread_pool, &mut old_thread_pool); ::std::mem::swap(&mut self._thread_pool, &mut old_thread_pool);
let _ = old_thread_pool.into_iter().map(|th| th.join()); let _ = old_thread_pool.into_iter().map(|th| th.join());
self.dnssec = None;
} }
/// Stop all workers, listeners /// Stop all workers, listeners
@ -148,7 +159,6 @@ impl Fenrir {
let mut old_thread_pool = Vec::new(); let mut old_thread_pool = Vec::new();
::std::mem::swap(&mut self._thread_pool, &mut old_thread_pool); ::std::mem::swap(&mut self._thread_pool, &mut old_thread_pool);
let _ = old_thread_pool.into_iter().map(|th| th.join()); let _ = old_thread_pool.into_iter().map(|th| th.join());
self.dnssec = None;
} }
/// Add all UDP sockets found in config /// Add all UDP sockets found in config
/// and start listening for packets /// and start listening for packets
@ -235,9 +245,9 @@ impl Fenrir {
} }
/// Get the raw TXT record of a Fenrir domain /// Get the raw TXT record of a Fenrir domain
pub async fn resolv_txt(&self, domain: &Domain) -> Result<String, Error> { pub async fn resolv_txt(&self, domain: &Domain) -> Result<String, Error> {
match &self.dnssec { match self.dnssec.resolv(domain).await {
Some(dnssec) => Ok(dnssec.resolv(domain).await?), Ok(res) => Ok(res),
None => Err(Error::NotInitialized), Err(e) => Err(e.into()),
} }
} }
@ -250,13 +260,22 @@ impl Fenrir {
Ok(dnssec::Dnssec::parse_txt_record(&record_str)?) Ok(dnssec::Dnssec::parse_txt_record(&record_str)?)
} }
/// Connect to a service /// Connect to a service, doing the dnssec resolution ourselves
pub async fn connect( pub async fn connect(
&self, &self,
domain: &Domain, domain: &Domain,
service: ServiceID, service: ServiceID,
) -> Result<(), Error> { ) -> Result<(), Error> {
let resolved = self.resolv(domain).await?; let resolved = self.resolv(domain).await?;
self.connect_resolved(resolved, domain, service).await
}
/// Connect to a service, with the user provided details
pub async fn connect_resolved(
&self,
resolved: dnssec::Record,
domain: &Domain,
service: ServiceID,
) -> Result<(), Error> {
loop { loop {
// check if we already have a connection to that auth. srv // check if we already have a connection to that auth. srv
let is_reserved = { let is_reserved = {
@ -354,6 +373,54 @@ impl Fenrir {
} }
} }
async fn start_single_worker(
&mut self,
) -> ::std::result::Result<
impl futures::Future<Output = Result<(), std::io::Error>>,
Error,
> {
let thread_idx = self._thread_work.len() as u16;
let max_threads = self.cfg.threads.unwrap().get() as u16;
if thread_idx >= max_threads {
::tracing::error!(
"thread id higher than number of threads in config"
);
assert!(
false,
"thread_idx is an index that can't reach cfg.threads"
);
return Err(Error::Setup("Thread id > threads_max".to_owned()));
}
let thread_id = ThreadTracker {
id: thread_idx,
total: max_threads,
};
let (work_send, work_recv) = ::async_channel::unbounded::<Work>();
let worker = Worker::new_and_loop(
self.cfg.clone(),
thread_id,
self.stop_working.subscribe(),
self.token_check.clone(),
self.cfg.listen.clone(),
work_recv,
);
loop {
let queues_lock = match Arc::get_mut(&mut self._thread_work) {
Some(queues_lock) => queues_lock,
None => {
// should not even ever happen
::tokio::time::sleep(::std::time::Duration::from_millis(
50,
))
.await;
continue;
}
};
queues_lock.push(work_send);
break;
}
Ok(worker)
}
// TODO: start work on a LocalSet provided by the user // TODO: start work on a LocalSet provided by the user
/// Start one working thread for each physical cpu /// Start one working thread for each physical cpu
/// threads are pinned to each cpu core. /// threads are pinned to each cpu core.

126
src/tests.rs Normal file
View File

@ -0,0 +1,126 @@
use crate::*;
#[::tokio::test]
async fn test_connection_dirsync() {
return;
use enc::asym::{KeyID, PrivKey, PubKey};
let rand = enc::Random::new();
let (priv_exchange_key, pub_exchange_key) =
match enc::asym::KeyExchangeKind::X25519DiffieHellman.new_keypair(&rand)
{
Ok((privkey, pubkey)) => {
(PrivKey::Exchange(privkey), PubKey::Exchange(pubkey))
}
Err(_) => {
assert!(false, "Can't generate random keypair");
return;
}
};
let dnssec_record = Record {
public_keys: [(KeyID(42), pub_exchange_key)].to_vec(),
addresses: [record::Address {
ip: ::std::net::IpAddr::V4(::std::net::Ipv4Addr::new(127, 0, 0, 1)),
port: Some(::core::num::NonZeroU16::new(31337).unwrap()),
priority: record::AddressPriority::P1,
weight: record::AddressWeight::W1,
handshake_ids: [HandshakeID::DirectorySynchronized].to_vec(),
public_key_idx: [record::PubKeyIdx(0)].to_vec(),
}]
.to_vec(),
key_exchanges: [enc::asym::KeyExchangeKind::X25519DiffieHellman]
.to_vec(),
hkdfs: [enc::hkdf::HkdfKind::Sha3].to_vec(),
ciphers: [enc::sym::CipherKind::XChaCha20Poly1305].to_vec(),
};
let cfg_client = {
let mut cfg = config::Config::default();
cfg.threads = Some(::core::num::NonZeroUsize::new(1).unwrap());
cfg
};
let cfg_server = {
let mut cfg = cfg_client.clone();
cfg.keys = [(KeyID(42), priv_exchange_key, pub_exchange_key)].to_vec();
cfg
};
let mut server = Fenrir::new(&cfg_server).unwrap();
let _ = server.setup_no_workers().await;
let srv_worker = server.start_single_worker().await;
::tokio::task::spawn_local(async move { srv_worker });
let mut client = Fenrir::new(&cfg_client).unwrap();
let _ = client.setup_no_workers().await;
let cli_worker = server.start_single_worker().await;
::tokio::task::spawn_local(async move { cli_worker });
use crate::{
connection::handshake::HandshakeID,
dnssec::{record, Record},
};
let _ = client
.connect_resolved(
dnssec_record,
&Domain("example.com".to_owned()),
auth::SERVICEID_AUTH,
)
.await;
/*
let thread_id = ThreadTracker { total: 1, id: 0 };
let (stop_sender, _) = ::tokio::sync::broadcast::channel::<bool>(1);
use ::std::net;
let cli_socket_addr = [net::SocketAddr::new(
net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)),
0,
)]
.to_vec();
let srv_socket_addr = [net::SocketAddr::new(
net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)),
0,
)]
.to_vec();
let srv_sock = Arc::new(connection::socket::bind_udp(srv_socket_addr[0])
.await
.unwrap());
let cli_sock = Arc::new(connection::socket::bind_udp(cli_socket_addr[0])
.await
.unwrap());
use crate::inner::worker::Work;
let (srv_work_send, srv_work_recv) = ::async_channel::unbounded::<Work>();
let (cli_work_send, cli_work_recv) = ::async_channel::unbounded::<Work>();
let srv_queue = Arc::new([srv_work_recv.clone()].to_vec());
let cli_queue = Arc::new([cli_work_recv.clone()].to_vec());
let listen_work_srv =
::tokio::spawn(Fenrir::listen_udp(
stop_sender.subscribe(),
let _server = crate::inner::worker::Worker::new(
cfg.clone(),
thread_id,
stop_sender.subscribe(),
None,
srv_socket_addr,
srv_work_recv,
);
let _client = crate::inner::worker::Worker::new(
cfg,
thread_id,
stop_sender.subscribe(),
None,
cli_socket_addr,
cli_work_recv,
);
todo!()
*/
}