More work on key exhcnage negotiation

Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
Luca Fulchir 2023-06-01 11:41:10 +02:00
parent 1bae4c9953
commit ac213a6528
Signed by: luca.fulchir
GPG Key ID: 8F6440603D13A78E
9 changed files with 350 additions and 108 deletions

View File

@ -1,6 +1,10 @@
//! //!
//! Configuration to initialize the Fenrir networking library //! Configuration to initialize the Fenrir networking library
use crate::{
connection::handshake::HandshakeID,
enc::{asym::KeyExchange, hkdf::HkdfKind, sym::CipherKind},
};
use ::std::{ use ::std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
num::NonZeroUsize, num::NonZeroUsize,
@ -18,6 +22,14 @@ pub struct Config {
pub listen: Vec<SocketAddr>, pub listen: Vec<SocketAddr>,
/// List of DNS resolvers to use /// List of DNS resolvers to use
pub resolvers: Vec<SocketAddr>, pub resolvers: Vec<SocketAddr>,
/// Supported handshakes
pub handshakes: Vec<HandshakeID>,
/// Supported key exchanges
pub key_exchanges: Vec<KeyExchange>,
/// Supported Hkdfs
pub hkdfs: Vec<HkdfKind>,
/// Supported Ciphers
pub ciphers: Vec<CipherKind>,
} }
impl Default for Config { impl Default for Config {
@ -34,6 +46,10 @@ impl Default for Config {
), ),
], ],
resolvers: Vec::new(), resolvers: Vec::new(),
handshakes: [HandshakeID::DirectorySynchronized].to_vec(),
key_exchanges: [KeyExchange::X25519DiffieHellman].to_vec(),
hkdfs: [HkdfKind::Sha3].to_vec(),
ciphers: [CipherKind::XChaCha20Poly1305].to_vec(),
} }
} }
} }

View File

@ -14,13 +14,41 @@ use crate::{
connection::{ProtocolVersion, ID}, connection::{ProtocolVersion, ID},
enc::{ enc::{
asym::{ExchangePubKey, KeyExchange, KeyID}, asym::{ExchangePubKey, KeyExchange, KeyID},
hkdf::HkdfKind,
sym::{CipherKind, HeadLen, Secret, TagLen}, sym::{CipherKind, HeadLen, Secret, TagLen},
Random,
}, },
}; };
use ::arrayref::array_mut_ref; use ::arrayref::array_mut_ref;
type Nonce = [u8; 16]; // TODO: merge with crate::enc::sym::Nonce
/// random nonce
#[derive(Debug, Clone, Copy)]
pub struct Nonce([u8; 16]);
impl Nonce {
/// Create a new random Nonce
pub fn new(rnd: &Random) -> Self {
use ::core::mem::MaybeUninit;
let mut out: MaybeUninit<[u8; 16]>;
#[allow(unsafe_code)]
unsafe {
out = MaybeUninit::uninit();
let _ = rnd.fill(out.assume_init_mut());
Self(out.assume_init())
}
}
/// Length of the serialized Nonce
pub const fn len() -> usize {
16
}
}
impl From<&[u8; 16]> for Nonce {
fn from(raw: &[u8; 16]) -> Self {
Self(raw.clone())
}
}
/// Parsed handshake /// Parsed handshake
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -61,6 +89,8 @@ pub struct Req {
pub key_id: KeyID, pub key_id: KeyID,
/// Selected key exchange /// Selected key exchange
pub exchange: KeyExchange, pub exchange: KeyExchange,
/// Selected hkdf
pub hkdf: HkdfKind,
/// Selected cipher /// Selected cipher
pub cipher: CipherKind, pub cipher: CipherKind,
/// Client ephemeral public key used for key exchanges /// Client ephemeral public key used for key exchanges
@ -77,6 +107,7 @@ impl Req {
ProtocolVersion::len() ProtocolVersion::len()
+ KeyID::len() + KeyID::len()
+ KeyExchange::len() + KeyExchange::len()
+ HkdfKind::len()
+ CipherKind::len() + CipherKind::len()
+ self.exchange_key.kind().pub_len() + self.exchange_key.kind().pub_len()
} }
@ -91,6 +122,7 @@ impl Req {
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
KeyID::len() KeyID::len()
+ KeyExchange::len() + KeyExchange::len()
+ HkdfKind::len()
+ CipherKind::len() + CipherKind::len()
+ self.exchange_key.kind().pub_len() + self.exchange_key.kind().pub_len()
+ self.data.len() + self.data.len()
@ -121,18 +153,23 @@ impl super::HandshakeParsing for Req {
Some(exchange) => exchange, Some(exchange) => exchange,
None => return Err(Error::Parsing), None => return Err(Error::Parsing),
}; };
let cipher: CipherKind = match CipherKind::from_u8(raw[3]) { let hkdf: HkdfKind = match HkdfKind::from_u8(raw[3]) {
Some(exchange) => exchange,
None => return Err(Error::Parsing),
};
let cipher: CipherKind = match CipherKind::from_u8(raw[4]) {
Some(cipher) => cipher, Some(cipher) => cipher,
None => return Err(Error::Parsing), None => return Err(Error::Parsing),
}; };
let (exchange_key, len) = match ExchangePubKey::from_slice(&raw[4..]) { let (exchange_key, len) = match ExchangePubKey::from_slice(&raw[5..]) {
Ok(exchange_key) => exchange_key, Ok(exchange_key) => exchange_key,
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
let data = ReqInner::CipherText(raw.len() - (4 + len)); let data = ReqInner::CipherText(raw.len() - (5 + len));
Ok(HandshakeData::DirSync(DirSync::Req(Self { Ok(HandshakeData::DirSync(DirSync::Req(Self {
key_id, key_id,
exchange, exchange,
hkdf,
cipher, cipher,
exchange_key, exchange_key,
data, data,
@ -253,7 +290,7 @@ pub struct ReqData {
impl ReqData { impl ReqData {
/// actual length of the request data /// actual length of the request data
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.nonce.len() + KeyID::len() + ID::len() + self.auth.len() Nonce::len() + KeyID::len() + ID::len() + self.auth.len()
} }
/// Minimum byte length of the request data /// Minimum byte length of the request data
pub const MIN_PKT_LEN: usize = pub const MIN_PKT_LEN: usize =
@ -265,7 +302,8 @@ impl ReqData {
} }
let mut start = 0; let mut start = 0;
let mut end = 16; let mut end = 16;
let nonce: Nonce = raw[start..end].try_into().unwrap(); let raw_sized: &[u8; 16] = raw[start..end].try_into().unwrap();
let nonce: Nonce = raw_sized.into();
start = end; start = end;
end = end + KeyID::len(); end = end + KeyID::len();
let client_key_id = let client_key_id =
@ -440,7 +478,7 @@ impl RespData {
assert!(out.len() == Self::len(), "wrong buffer size"); assert!(out.len() == Self::len(), "wrong buffer size");
let mut start = 0; let mut start = 0;
let mut end = Self::NONCE_LEN; let mut end = Self::NONCE_LEN;
out[start..end].copy_from_slice(&self.client_nonce); out[start..end].copy_from_slice(&self.client_nonce.0);
start = end; start = end;
end = end + Self::NONCE_LEN; end = end + Self::NONCE_LEN;
self.id.serialize(&mut out[start..end]); self.id.serialize(&mut out[start..end]);

View File

@ -27,6 +27,37 @@ pub enum Error {
NotEnoughData, NotEnoughData,
} }
/// List of possible handshakes
#[derive(::num_derive::FromPrimitive, Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum HandshakeID {
/// 1-RTT Directory synchronized handshake. Fast, no forward secrecy
DirectorySynchronized = 0,
/// 2-RTT Stateful exchange. Little DDos protection
Stateful,
/// 3-RTT stateless exchange. Forward secrecy and ddos protection
Stateless,
}
impl TryFrom<&str> for HandshakeID {
type Error = ::std::io::Error;
// TODO: from actual names, not only numeric
fn try_from(raw: &str) -> Result<Self, Self::Error> {
if let Ok(handshake_u8) = raw.parse::<u8>() {
if handshake_u8 >= 1 {
if let Some(handshake) = HandshakeID::from_u8(handshake_u8 - 1)
{
return Ok(handshake);
}
}
}
return Err(::std::io::Error::new(
::std::io::ErrorKind::InvalidData,
"Unknown handshake ID",
));
}
}
pub(crate) struct HandshakeServer { pub(crate) struct HandshakeServer {
pub id: crate::enc::asym::KeyID, pub id: crate::enc::asym::KeyID,
pub key: crate::enc::asym::PrivKey, pub key: crate::enc::asym::PrivKey,

View File

@ -19,15 +19,15 @@
//! * 2..4 priority (for failover) //! * 2..4 priority (for failover)
//! * 5..7 weight between priority //! * 5..7 weight between priority
//! * 1 byte: divided in half: //! * 1 byte: divided in half:
//! * half: num of public key ids //! * half: num of public key indexes
//! * half: num of handhskae ids //! * half: num of handshake ids
//! * 2 bytes: UDP port //! * 2 bytes: UDP port
//! * [ 1 byte per public key id ] //! * [ HALF byte per public key idx ] (index on the list of public keys)
//! * [ 1 byte per handshake id ] //! * [ 1 byte per handshake id ]
//! * X bytes: IP //! * X bytes: IP
//! ] //! ]
//! [ # list of pubkeys //! [ # list of pubkeys (max: 16)
//! * 1 byte: pubkey id //! * 2 byte: pubkey id
//! * 1 byte: pubkey length //! * 1 byte: pubkey length
//! * 1 byte: pubkey type //! * 1 byte: pubkey type
//! * Y bytes: pubkey //! * Y bytes: pubkey
@ -42,11 +42,14 @@
//! * 1 byte for each cipher //! * 1 byte for each cipher
//! ] //! ]
use crate::enc::{ use crate::{
connection::handshake::HandshakeID,
enc::{
self, self,
asym::{KeyExchange, PubKey}, asym::{KeyExchange, KeyID, PubKey},
hkdf::HkdfKind, hkdf::HkdfKind,
sym::CipherKind, sym::CipherKind,
},
}; };
use ::core::num::NonZeroU16; use ::core::num::NonZeroU16;
use ::num_traits::FromPrimitive; use ::num_traits::FromPrimitive;
@ -55,22 +58,12 @@ use ::std::{net::IpAddr, vec::Vec};
* Public key data * Public key data
*/ */
/// Public Key ID /// Public Key Index.
/// this points to the index of the public key in the array of public keys.
/// needed to have one byte less in the list of public keys
/// supported by and address
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct PublicKeyID(u8); pub struct PubKeyIdx(pub u8);
impl TryFrom<&str> for PublicKeyID {
type Error = ::std::io::Error;
fn try_from(raw: &str) -> Result<Self, Self::Error> {
if let Ok(id_u8) = raw.parse::<u8>() {
return Ok(PublicKeyID(id_u8));
}
return Err(::std::io::Error::new(
::std::io::ErrorKind::InvalidData,
"Public Key ID must be between 0 and 256",
));
}
}
/* /*
* Address data * Address data
@ -168,37 +161,6 @@ impl TryFrom<&str> for AddressWeight {
} }
} }
/// List of possible handshakes
#[derive(::num_derive::FromPrimitive, Debug, Clone, Copy)]
#[repr(u8)]
pub enum HandshakeID {
/// 1-RTT Directory synchronized handshake. Fast, no forward secrecy
DirectorySynchronized = 0,
/// 2-RTT Stateful exchange. Little DDos protection
Stateful,
/// 3-RTT stateless exchange. Forward secrecy and ddos protection
Stateless,
}
impl TryFrom<&str> for HandshakeID {
type Error = ::std::io::Error;
// TODO: from actual names, not only numeric
fn try_from(raw: &str) -> Result<Self, Self::Error> {
if let Ok(handshake_u8) = raw.parse::<u8>() {
if handshake_u8 >= 1 {
if let Some(handshake) = HandshakeID::from_u8(handshake_u8 - 1)
{
return Ok(handshake);
}
}
}
return Err(::std::io::Error::new(
::std::io::ErrorKind::InvalidData,
"Unknown handshake ID",
));
}
}
/// Authentication server address information: /// Authentication server address information:
/// * ip /// * ip
/// * udp port /// * udp port
@ -220,14 +182,16 @@ pub struct Address {
/// List of supported handshakes /// List of supported handshakes
pub handshake_ids: Vec<HandshakeID>, pub handshake_ids: Vec<HandshakeID>,
/// Public key IDs used by this address /// Public key IDs used by this address
pub public_key_ids: Vec<PublicKeyID>, pub public_key_idx: Vec<PubKeyIdx>,
} }
impl Address { impl Address {
fn raw_len(&self) -> usize { fn raw_len(&self) -> usize {
// UDP port + Priority + Weight + pubkey_len + handshake_len // UDP port + Priority + Weight + pubkey_len + handshake_len
let mut size = 6; let mut size = 6;
size = size + self.public_key_ids.len() + self.handshake_ids.len(); let num_pubkey_idx = self.public_key_idx.len();
let idx_bytes = (num_pubkey_idx / 2) + (num_pubkey_idx % 2);
size = size + idx_bytes + self.handshake_ids.len();
size + match self.ip { size + match self.ip {
IpAddr::V4(_) => size + 4, IpAddr::V4(_) => size + 4,
IpAddr::V6(_) => size + 16, IpAddr::V6(_) => size + 16,
@ -244,7 +208,7 @@ impl Address {
bitfield |= self.weight as u8; bitfield |= self.weight as u8;
raw.push(bitfield); raw.push(bitfield);
let len_combined: u8 = self.public_key_ids.len() as u8; let len_combined: u8 = self.public_key_idx.len() as u8;
let len_combined = len_combined << 4; let len_combined = len_combined << 4;
let len_combined = len_combined | self.handshake_ids.len() as u8; let len_combined = len_combined | self.handshake_ids.len() as u8;
raw.push(len_combined); raw.push(len_combined);
@ -256,8 +220,18 @@ impl Address {
}), }),
); );
for id in self.public_key_ids.iter() { // pair every idx, since the max is 16
raw.push(id.0); for chunk in self.public_key_idx.chunks(2) {
let second = {
if chunk.len() == 2 {
chunk[1].0
} else {
0
}
};
let tmp = chunk[0].0 << 4;
let tmp = tmp | second;
raw.push(tmp);
} }
for id in self.handshake_ids.iter() { for id in self.handshake_ids.iter() {
raw.push(*id as u8); raw.push(*id as u8);
@ -301,21 +275,29 @@ impl Address {
}; };
// Add publickey ids // Add publickey ids
let num_pubkey_ids = (raw[3] >> 4) as usize; let num_pubkey_idx = (raw[3] >> 4) as usize;
let num_handshake_ids = (raw[3] & 0x0F) as usize; let num_handshake_ids = (raw[3] & 0x0F) as usize;
if raw.len() <= 3 + num_pubkey_ids + num_handshake_ids { if raw.len() <= 3 + num_pubkey_idx + num_handshake_ids {
return Err(Error::NotEnoughData(3)); return Err(Error::NotEnoughData(3));
} }
let mut bytes_parsed = 4; let mut bytes_parsed = 4;
let mut public_key_ids = Vec::with_capacity(num_pubkey_ids); let mut public_key_idx = Vec::with_capacity(num_pubkey_idx);
for raw_pubkey_id in let idx_bytes = (num_pubkey_idx / 2) + (num_pubkey_idx % 2);
raw[bytes_parsed..(bytes_parsed + num_pubkey_ids)].iter() let mut idx_added = 0;
for raw_pubkey_idx_pair in
raw[bytes_parsed..(bytes_parsed + idx_bytes)].iter()
{ {
public_key_ids.push(PublicKeyID(*raw_pubkey_id)); let first = PubKeyIdx(raw_pubkey_idx_pair >> 4);
let second = PubKeyIdx(raw_pubkey_idx_pair & 0x0F);
public_key_idx.push(first);
if num_pubkey_idx - idx_added >= 2 {
public_key_idx.push(second);
}
idx_added = idx_added + 2;
} }
// add handshake ids // add handshake ids
bytes_parsed = bytes_parsed + num_pubkey_ids; bytes_parsed = bytes_parsed + idx_bytes;
let mut handshake_ids = Vec::with_capacity(num_handshake_ids); let mut handshake_ids = Vec::with_capacity(num_handshake_ids);
for raw_handshake_id in for raw_handshake_id in
raw[bytes_parsed..(bytes_parsed + num_handshake_ids)].iter() raw[bytes_parsed..(bytes_parsed + num_handshake_ids)].iter()
@ -358,7 +340,7 @@ impl Address {
port, port,
priority, priority,
weight, weight,
public_key_ids, public_key_idx,
handshake_ids, handshake_ids,
}, },
bytes_parsed, bytes_parsed,
@ -374,7 +356,7 @@ impl Address {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Record { pub struct Record {
/// Public keys used by any authentication server /// Public keys used by any authentication server
pub public_keys: Vec<(PublicKeyID, PubKey)>, pub public_keys: Vec<(KeyID, PubKey)>,
/// List of all authentication servers' addresses. /// List of all authentication servers' addresses.
/// Multiple ones can point to the same authentication server /// Multiple ones can point to the same authentication server
pub addresses: Vec<Address>, pub addresses: Vec<Address>,
@ -443,7 +425,8 @@ impl Record {
address.encode_into(&mut raw); address.encode_into(&mut raw);
} }
for (public_key_id, public_key) in self.public_keys.iter() { for (public_key_id, public_key) in self.public_keys.iter() {
raw.push(public_key_id.0); let key_id_bytes = public_key_id.0.to_le_bytes();
raw.extend_from_slice(&key_id_bytes);
raw.push(public_key.kind().pub_len() as u8); raw.push(public_key.kind().pub_len() as u8);
raw.push(public_key.kind() as u8); raw.push(public_key.kind() as u8);
public_key.serialize_into(&mut raw); public_key.serialize_into(&mut raw);
@ -462,8 +445,8 @@ impl Record {
} }
/// Decode from base85 to the actual object /// Decode from base85 to the actual object
pub fn decode(raw: &[u8]) -> Result<Self, Error> { pub fn decode(raw: &[u8]) -> Result<Self, Error> {
// bare minimum for 1 address, 1 key, 1 key exchange and 1 cipher // bare minimum for lengths, (1 address), (1 key), cipher negotiation
const MIN_RAW_LENGTH: usize = 1 + 1 + 1 + 8 + 9 + 1 + 1; const MIN_RAW_LENGTH: usize = 3 + (6 + 4) + (4 + 32) + 1 + 1 + 1;
if raw.len() <= MIN_RAW_LENGTH { if raw.len() <= MIN_RAW_LENGTH {
return Err(Error::NotEnoughData(0)); return Err(Error::NotEnoughData(0));
} }
@ -499,11 +482,14 @@ impl Record {
num_addresses = num_addresses - 1; num_addresses = num_addresses - 1;
} }
while num_public_keys > 0 { while num_public_keys > 0 {
if bytes_parsed + 2 >= raw.len() { if bytes_parsed + 3 >= raw.len() {
return Err(Error::NotEnoughData(bytes_parsed)); return Err(Error::NotEnoughData(bytes_parsed));
} }
let id = PublicKeyID(raw[bytes_parsed]);
bytes_parsed = bytes_parsed + 1; let raw_key_id =
u16::from_le_bytes([raw[bytes_parsed], raw[bytes_parsed + 1]]);
let id = KeyID(raw_key_id);
bytes_parsed = bytes_parsed + 2;
let pubkey_length = raw[bytes_parsed] as usize; let pubkey_length = raw[bytes_parsed] as usize;
bytes_parsed = bytes_parsed + 1; bytes_parsed = bytes_parsed + 1;
if pubkey_length + bytes_parsed >= raw.len() { if pubkey_length + bytes_parsed >= raw.len() {
@ -590,6 +576,13 @@ impl Record {
result.ciphers.push(cipher); result.ciphers.push(cipher);
num_ciphers = num_ciphers - 1; num_ciphers = num_ciphers - 1;
} }
for addr in result.addresses.iter() {
for idx in addr.public_key_idx.iter() {
if idx.0 as usize >= result.public_keys.len() {
return Err(Error::Max16PublicKeys);
}
}
}
if bytes_parsed != raw.len() { if bytes_parsed != raw.len() {
Err(Error::UnknownData(bytes_parsed)) Err(Error::UnknownData(bytes_parsed))
} else { } else {

View File

@ -3,7 +3,10 @@
use ::num_traits::FromPrimitive; use ::num_traits::FromPrimitive;
use super::Error; use super::Error;
use crate::enc::{sym::Secret, Random}; use crate::{
config::Config,
enc::{sym::Secret, Random},
};
/// Public key ID /// Public key ID
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
@ -68,8 +71,19 @@ impl KeyKind {
KeyKind::X25519 => KeyCapabilities::Exchange, KeyKind::X25519 => KeyCapabilities::Exchange,
} }
} }
/// Returns the key exchanges supported by this key
pub fn key_exchanges(&self) -> &'static [KeyExchange] {
const EMPTY: [KeyExchange; 0] = [];
const X25519_KEY_EXCHANGES: [KeyExchange; 1] =
[KeyExchange::X25519DiffieHellman];
match self {
KeyKind::Ed25519 => &EMPTY,
KeyKind::X25519 => &X25519_KEY_EXCHANGES,
}
}
} }
// FIXME: rename in KeyExchangeKind
/// Kind of key exchange /// Kind of key exchange
#[derive(Debug, Copy, Clone, PartialEq, ::num_derive::FromPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, ::num_derive::FromPrimitive)]
#[non_exhaustive] #[non_exhaustive]
@ -279,7 +293,28 @@ impl ExchangePubKey {
} }
} }
/// Build a new pair of private/public key pair /// Select the best key exchange from our supported list
pub fn new_keypair(kind: KeyKind, rnd: &Random) -> (PrivKey, PubKey) { /// and the other endpoint supported list.
todo!() /// Give priority to our list
pub fn server_select_key_exchange(
cfg: &Config,
client_supported: &Vec<KeyExchange>,
) -> Option<KeyExchange> {
cfg.key_exchanges
.iter()
.find(|k| client_supported.contains(k))
.copied()
}
/// Select the best key exchange from our supported list
/// and the other endpoint supported list.
/// Give priority to the server list
/// This is used only in the Directory Synchronized handshake
pub fn client_select_key_exchange(
cfg: &Config,
server_supported: &Vec<KeyExchange>,
) -> Option<KeyExchange> {
server_supported
.iter()
.find(|k| cfg.key_exchanges.contains(k))
.copied()
} }

View File

@ -4,7 +4,7 @@
use ::sha3::Sha3_256; use ::sha3::Sha3_256;
use ::zeroize::Zeroize; use ::zeroize::Zeroize;
use crate::enc::sym::Secret; use crate::{config::Config, enc::sym::Secret};
/// Kind of HKDF /// Kind of HKDF
#[derive(Debug, Copy, Clone, PartialEq, ::num_derive::FromPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, ::num_derive::FromPrimitive)]
@ -14,6 +14,12 @@ pub enum HkdfKind {
/// Sha3 /// Sha3
Sha3 = 0, Sha3 = 0,
} }
impl HkdfKind {
/// Length of the serialized type
pub const fn len() -> usize {
1
}
}
/// Generic wrapper on Hkdfs /// Generic wrapper on Hkdfs
#[derive(Clone)] #[derive(Clone)]
@ -52,6 +58,8 @@ impl Hkdf {
// we can't use #[derive(Zeroing)] either. // we can't use #[derive(Zeroing)] either.
// So we craete a union with a Zeroing object, and drop both manually. // So we craete a union with a Zeroing object, and drop both manually.
// TODO: move this to Hkdf instead of Sha3
#[derive(Zeroize)] #[derive(Zeroize)]
#[zeroize(drop)] #[zeroize(drop)]
struct Zeroable([u8; ::core::mem::size_of::<::hkdf::Hkdf<Sha3_256>>()]); struct Zeroable([u8; ::core::mem::size_of::<::hkdf::Hkdf<Sha3_256>>()]);
@ -89,7 +97,7 @@ pub struct HkdfSha3 {
impl HkdfSha3 { impl HkdfSha3 {
/// Instantiate a new HKDF with Sha3-256 /// Instantiate a new HKDF with Sha3-256
pub fn new(salt: &[u8], key: Secret) -> Self { pub(crate) fn new(salt: &[u8], key: Secret) -> Self {
let hkdf = ::hkdf::Hkdf::<Sha3_256>::new(Some(salt), key.as_ref()); let hkdf = ::hkdf::Hkdf::<Sha3_256>::new(Some(salt), key.as_ref());
Self { Self {
inner: HkdfInner { inner: HkdfInner {
@ -98,7 +106,7 @@ impl HkdfSha3 {
} }
} }
/// Get a secret generated from the key and a given context /// Get a secret generated from the key and a given context
pub fn get_secret(&self, context: &[u8]) -> Secret { pub(crate) fn get_secret(&self, context: &[u8]) -> Secret {
let mut out: [u8; 32] = [0; 32]; let mut out: [u8; 32] = [0; 32];
#[allow(unsafe_code)] #[allow(unsafe_code)]
unsafe { unsafe {
@ -117,3 +125,29 @@ impl ::core::fmt::Debug for HkdfSha3 {
::core::fmt::Debug::fmt("[hidden hkdf]", f) ::core::fmt::Debug::fmt("[hidden hkdf]", f)
} }
} }
/// Select the best hkdf from our supported list
/// and the other endpoint supported list.
/// Give priority to our list
pub fn server_select_hkdf(
cfg: &Config,
client_supported: &Vec<HkdfKind>,
) -> Option<HkdfKind> {
cfg.hkdfs
.iter()
.find(|h| client_supported.contains(h))
.copied()
}
/// Select the best hkdf from our supported list
/// and the other endpoint supported list.
/// Give priority to the server list
/// this is used only in the directory synchronized handshake
pub fn client_select_hkdf(
cfg: &Config,
server_supported: &Vec<HkdfKind>,
) -> Option<HkdfKind> {
server_supported
.iter()
.find(|h| cfg.hkdfs.contains(h))
.copied()
}

View File

@ -1,7 +1,7 @@
//! Symmetric cypher stuff //! Symmetric cypher stuff
use super::Error; use super::Error;
use crate::enc::Random; use crate::{config::Config, enc::Random};
use ::zeroize::Zeroize; use ::zeroize::Zeroize;
/// Secret, used for keys. /// Secret, used for keys.
@ -299,7 +299,7 @@ impl XChaCha20Poly1305 {
} }
// //
// TODO: For efficiency "Nonce" should become a reference. // TODO: Merge crate::{enc::sym::Nonce, connection::handshake::dirsync::Nonce}
// //
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -387,3 +387,28 @@ impl NonceSync {
old_nonce old_nonce
} }
} }
/// Select the best cipher from our supported list
/// and the other endpoint supported list.
/// Give priority to our list
pub fn server_select_cipher(
cfg: &Config,
client_supported: &Vec<CipherKind>,
) -> Option<CipherKind> {
cfg.ciphers
.iter()
.find(|c| client_supported.contains(c))
.copied()
}
/// Select the best cipher from our supported list
/// and the other endpoint supported list.
/// Give priority to the server list
/// This is used only in the Directory synchronized handshake
pub fn client_select_cipher(
cfg: &Config,
server_supported: &Vec<CipherKind>,
) -> Option<CipherKind> {
server_supported
.iter()
.find(|c| cfg.ciphers.contains(c))
.copied()
}

View File

@ -1,6 +1,7 @@
//! Worker thread implementation //! Worker thread implementation
use crate::{ use crate::{
auth::{ServiceID, TokenChecker}, auth::{ServiceID, TokenChecker},
config::Config,
connection::{ connection::{
self, self,
handshake::{ handshake::{
@ -12,9 +13,9 @@ use crate::{
}, },
dnssec, dnssec,
enc::{ enc::{
asym::PubKey, asym::{self, PubKey},
hkdf::{Hkdf, HkdfKind}, hkdf::{self, Hkdf, HkdfKind},
sym::Secret, sym::{self, Secret},
Random, Random,
}, },
inner::{HandshakeAction, HandshakeTracker, ThreadTracker}, inner::{HandshakeAction, HandshakeTracker, ThreadTracker},
@ -53,6 +54,7 @@ pub(crate) enum WorkAnswer {
/// Actual worker implementation. /// Actual worker implementation.
pub(crate) struct Worker { pub(crate) struct Worker {
cfg: Config,
thread_id: ThreadTracker, thread_id: ThreadTracker,
// PERF: rand uses syscalls. how to do that async? // PERF: rand uses syscalls. how to do that async?
rand: Random, rand: Random,
@ -67,6 +69,7 @@ pub(crate) struct Worker {
impl Worker { impl Worker {
pub(crate) async fn new_and_loop( pub(crate) async fn new_and_loop(
cfg: Config,
thread_id: ThreadTracker, thread_id: ThreadTracker,
stop_working: ::tokio::sync::broadcast::Receiver<bool>, stop_working: ::tokio::sync::broadcast::Receiver<bool>,
token_check: Option<Arc<Mutex<TokenChecker>>>, token_check: Option<Arc<Mutex<TokenChecker>>>,
@ -75,6 +78,7 @@ impl Worker {
) -> ::std::io::Result<()> { ) -> ::std::io::Result<()> {
// TODO: get a channel to send back information, and send the error // TODO: get a channel to send back information, and send the error
let mut worker = Self::new( let mut worker = Self::new(
cfg,
thread_id, thread_id,
stop_working, stop_working,
token_check, token_check,
@ -86,6 +90,7 @@ impl Worker {
Ok(()) Ok(())
} }
pub(crate) async fn new( pub(crate) async fn new(
cfg: Config,
thread_id: ThreadTracker, thread_id: ThreadTracker,
stop_working: ::tokio::sync::broadcast::Receiver<bool>, stop_working: ::tokio::sync::broadcast::Receiver<bool>,
token_check: Option<Arc<Mutex<TokenChecker>>>, token_check: Option<Arc<Mutex<TokenChecker>>>,
@ -126,6 +131,7 @@ impl Worker {
}; };
Ok(Self { Ok(Self {
cfg,
thread_id, thread_id,
rand: Random::new(), rand: Random::new(),
stop_working, stop_working,
@ -156,19 +162,52 @@ impl Worker {
let _ = sender.send(conn_num); let _ = sender.send(conn_num);
} }
Work::Connect((send_res, dnssec_record, _service_id)) => { Work::Connect((send_res, dnssec_record, _service_id)) => {
// PERF: geolocation
// Find the first destination with a coherent
// pubkey/key exchange
let destination = let destination =
dnssec_record.addresses.iter().find_map(|addr| { dnssec_record.addresses.iter().find_map(|addr| {
let maybe_key = if addr
dnssec_record.public_keys.iter().find( .handshake_ids
|(id, _)| addr.public_key_ids.contains(id), .iter()
); .find(|h_srv| {
match maybe_key { self.cfg.handshakes.contains(h_srv)
Some(key) => Some((addr, key)), })
None => None, .is_none()
{
// skip servers with no corresponding
// handshake types
return None;
} }
for idx in addr.public_key_idx.iter() {
let key_supported_k_x =
dnssec_record.public_keys[idx.0 as usize]
.1
.kind()
.key_exchanges();
match self
.cfg
.key_exchanges
.iter()
.find(|x| key_supported_k_x.contains(x))
{
Some(exchange) => {
return Some((
addr,
dnssec_record.public_keys
[idx.0 as usize],
exchange,
))
}
None => return None,
}
}
return None;
}); });
let (addr, key) = match destination { let (addr, key, exchange) = match destination {
Some((addr, key)) => (addr, key), Some((addr, key, exchange)) => (addr, key, exchange),
None => { None => {
let _ = let _ =
send_res.send(Err(crate::Error::Resolution( send_res.send(Err(crate::Error::Resolution(
@ -178,8 +217,29 @@ impl Worker {
continue 'mainloop; continue 'mainloop;
} }
}; };
use crate::enc::asym; let hkdf = match hkdf::client_select_hkdf(
let exchange = asym::KeyExchange::X25519DiffieHellman; &self.cfg,
&dnssec_record.hkdfs,
) {
Some(hkdf) => hkdf,
None => {
let _ = send_res
.send(Err(crate::Error::HandshakeNegotiation));
continue 'mainloop;
}
};
let cipher = match sym::client_select_cipher(
&self.cfg,
&dnssec_record.ciphers,
) {
Some(cipher) => cipher,
None => {
let _ = send_res
.send(Err(crate::Error::HandshakeNegotiation));
continue 'mainloop;
}
};
let (priv_key, pub_key) = let (priv_key, pub_key) =
match exchange.new_keypair(&self.rand) { match exchange.new_keypair(&self.rand) {
Ok(pair) => pair, Ok(pair) => pair,
@ -187,10 +247,15 @@ impl Worker {
}; };
// build request // build request
/* /*
let req_data = dirsync::ReqData {
nonce: dirsync::Nonce::new(&self.rand),
client_key_id:
};
let req = dirsync::Req { let req = dirsync::Req {
key_id: key.0, key_id: key.0,
exchange: exchange, exchange,
cipher: 42, hkdf,
cipher,
exchange_key: client_pub_key, exchange_key: client_pub_key,
data: 42, data: 42,
}; };

View File

@ -62,6 +62,9 @@ pub enum Error {
/// Resolution problems. wrong or incomplete DNSSEC data /// Resolution problems. wrong or incomplete DNSSEC data
#[error("DNSSEC resolution: {0}")] #[error("DNSSEC resolution: {0}")]
Resolution(String), Resolution(String),
/// No common cryptographic primitives
#[error("No common cryptographic primitives")]
HandshakeNegotiation,
} }
/// Instance of a fenrir endpoint /// Instance of a fenrir endpoint
@ -381,6 +384,7 @@ impl Fenrir {
::tracing::debug!("Spawning thread {}", core); ::tracing::debug!("Spawning thread {}", core);
let th_topology = hw_topology.clone(); let th_topology = hw_topology.clone();
let th_tokio_rt = tokio_rt.clone(); let th_tokio_rt = tokio_rt.clone();
let th_config = self.cfg.clone();
let (work_send, work_recv) = ::async_channel::unbounded::<Work>(); let (work_send, work_recv) = ::async_channel::unbounded::<Work>();
let th_stop_working = self.stop_working.subscribe(); let th_stop_working = self.stop_working.subscribe();
let th_token_check = self.token_check.clone(); let th_token_check = self.token_check.clone();
@ -417,6 +421,7 @@ impl Fenrir {
let _ = tk_local.block_on( let _ = tk_local.block_on(
&th_tokio_rt, &th_tokio_rt,
Worker::new_and_loop( Worker::new_and_loop(
th_config,
thread_id, thread_id,
th_stop_working, th_stop_working,
th_token_check, th_token_check,