token check function stubs

Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
Luca Fulchir 2023-02-21 22:06:17 +01:00
parent bb348f392e
commit f5a605867e
Signed by: luca.fulchir
GPG Key ID: 8F6440603D13A78E
8 changed files with 328 additions and 47 deletions

View File

@ -28,15 +28,17 @@
fd fd
#(rust-bin.stable.latest.default.override { #(rust-bin.stable.latest.default.override {
# go with nightly to have async fn in traits # go with nightly to have async fn in traits
(rust-bin.nightly."2023-02-01".default.override { #(rust-bin.nightly."2023-02-01".default.override {
#extensions = [ "rust-src" ]; # #extensions = [ "rust-src" ];
#targets = [ "arm-unknown-linux-gnueabihf" ]; # #targets = [ "arm-unknown-linux-gnueabihf" ];
}) #})
clippy clippy
lld
cargo-watch cargo-watch
rustfmt
cargo-license cargo-license
lld
rust-bin.stable.latest.default
rustfmt
rust-analyzer
]; ];
shellHook = '' shellHook = ''
# use zsh or other custom shell # use zsh or other custom shell

44
src/auth/mod.rs Normal file
View File

@ -0,0 +1,44 @@
//! Authentication reslated struct definitions
/// User identifier. 16 bytes for easy uuid conversion
#[derive(Debug, Copy, Clone)]
pub struct UserID([u8; 16]);
impl From<[u8; 16]> for UserID {
fn from(raw: [u8; 16]) -> Self {
UserID(raw)
}
}
impl UserID {
/// length of the User ID in bytes
pub const fn len() -> usize {
16
}
}
/// Authentication Token, basically just 32 random bytes
#[derive(Copy, Clone)]
pub struct Token([u8; 32]);
impl Token {
/// length of the token in bytes
pub const fn len() -> usize {
32
}
}
impl From<[u8; 32]> for Token {
fn from(raw: [u8; 32]) -> Self {
Token(raw)
}
}
// Fake debug implementation to avoid leaking tokens
impl ::core::fmt::Debug for Token {
fn fmt(
&self,
f: &mut core::fmt::Formatter<'_>,
) -> Result<(), ::std::fmt::Error> {
::core::fmt::Debug::fmt("[hidden token]", f)
}
}

View File

@ -10,13 +10,16 @@
use super::{Error, HandshakeData}; use super::{Error, HandshakeData};
use crate::{ use crate::{
auth,
connection::ID, connection::ID,
enc::{ enc::{
asym::{ExchangePubKey, KeyExchange, KeyID}, asym::{ExchangePubKey, KeyExchange, KeyID},
sym::CipherKind, sym::CipherKind,
}, },
}; };
use ::std::vec::Vec; use ::std::{collections::VecDeque, num::NonZeroU64, vec::Vec};
type Nonce = [u8; 16];
/// Parsed handshake /// Parsed handshake
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -39,7 +42,14 @@ pub struct Req {
/// Client ephemeral public key used for key exchanges /// Client ephemeral public key used for key exchanges
pub exchange_key: ExchangePubKey, pub exchange_key: ExchangePubKey,
/// encrypted data /// encrypted data
pub enc: Vec<u8>, pub data: ReqInner,
}
impl Req {
/// Set the cleartext data after it was parsed
pub fn set_data(&mut self, data: ReqData) {
self.data = ReqInner::Data(data);
}
} }
impl super::HandshakeParsing for Req { impl super::HandshakeParsing for Req {
@ -63,30 +73,102 @@ impl super::HandshakeParsing for Req {
Ok(exchange_key) => exchange_key, Ok(exchange_key) => exchange_key,
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
let enc = raw[(4 + len)..].to_vec(); let mut vec = VecDeque::with_capacity(raw.len() - (4 + len));
vec.extend(raw[(4 + len)..].iter().copied());
let _ = vec.make_contiguous();
let data = ReqInner::Ciphertext(vec);
Ok(HandshakeData::DirSync(DirSync::Req(Self { Ok(HandshakeData::DirSync(DirSync::Req(Self {
key_id, key_id,
exchange, exchange,
cipher, cipher,
exchange_key, exchange_key,
enc, data,
}))) })))
} }
} }
/// Quick way to avoid mixing cipher and clear text
#[derive(Debug, Clone)]
pub enum ReqInner {
/// Client data, still in ciphertext
Ciphertext(VecDeque<u8>),
/// Client data, decrypted but unprocessed
Cleartext(VecDeque<u8>),
/// Parsed client data
Data(ReqData),
}
impl ReqInner {
/// Get the ciptertext, or panic
pub fn ciphertext<'a>(&'a mut self) -> &'a mut VecDeque<u8> {
match self {
ReqInner::Ciphertext(data) => data,
_ => panic!(),
}
}
/// switch from ciphertext to cleartext
pub fn mark_as_cleartext(&mut self) {
let mut newdata: VecDeque<u8>;
match self {
ReqInner::Ciphertext(data) => {
newdata = VecDeque::new();
::core::mem::swap(&mut newdata, data);
}
_ => return,
}
*self = ReqInner::Cleartext(newdata);
}
}
/// Decrypted request data /// Decrypted request data
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct ReqData { pub struct ReqData {
/// Random nonce, the client can use this to track multiple key exchanges /// Random nonce, the client can use this to track multiple key exchanges
pub nonce: [u8; 16], pub nonce: Nonce,
/// Client key id so the client can use and rotate keys /// Client key id so the client can use and rotate keys
pub client_key_id: KeyID, pub client_key_id: KeyID,
/// User of the domain
pub user: auth::UserID,
/// Authentication token /// Authentication token
pub token: [u8; 32], pub token: auth::Token,
/// Receiving connection id for the client /// Receiving connection id for the client
pub id: ID, pub id: ID,
// TODO: service info // TODO: service info
} }
impl ReqData {
/// Parse the cleartext raw data
pub fn parse(raw: &ReqInner) -> Result<Self, Error> {
const MIN_PKT_LEN: usize = 16
+ KeyID::len()
+ auth::UserID::len()
+ auth::Token::len()
+ ID::len();
let raw = match raw {
ReqInner::Cleartext(raw) => raw.as_slices().0,
_ => return Err(Error::Parsing),
};
if raw.len() < MIN_PKT_LEN {
return Err(Error::NotEnoughData);
}
let nonce: Nonce = raw.try_into().unwrap();
let client_key_id =
KeyID(u16::from_le_bytes(raw[16..17].try_into().unwrap()));
let raw_user: [u8; 16] = raw[18..34].try_into().unwrap();
let user: auth::UserID = raw_user.into();
let raw_token: [u8; 32] = raw[34..66].try_into().unwrap();
let token: auth::Token = raw_token.into();
let id: ID = u64::from_le_bytes(raw[66..74].try_into().unwrap()).into();
if id.is_handshake() {
return Err(Error::Parsing);
}
Ok(Self {
nonce,
client_key_id,
user,
token,
id,
})
}
}
/// Server response in a directory synchronized handshake /// Server response in a directory synchronized handshake
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -107,7 +189,7 @@ impl super::HandshakeParsing for Resp {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct RespData { pub struct RespData {
/// Client nonce, copied from the request /// Client nonce, copied from the request
client_nonce: [u8; 16], client_nonce: Nonce,
/// Server Connection ID /// Server Connection ID
id: ID, id: ID,
/// Service Connection ID /// Service Connection ID
@ -115,3 +197,27 @@ pub struct RespData {
/// Service encryption key /// Service encryption key
service_key: [u8; 32], service_key: [u8; 32],
} }
impl RespData {
const NONCE_LEN: usize = ::core::mem::size_of::<Nonce>();
/// Return the expected length for buffer allocation
pub fn len() -> usize {
Self::NONCE_LEN + ID::len() + ID::len() + 32
}
/// Serialize the data into a buffer
pub fn serialize(&self, out: &mut [u8]) {
assert!(out.len() == Self::len(), "wrong buffer size");
let mut start = 0;
let mut end = Self::NONCE_LEN;
out[start..end].copy_from_slice(&self.client_nonce);
start = end;
end = end + Self::NONCE_LEN;
self.id.serialize(&mut out[start..end]);
start = end;
end = end + Self::NONCE_LEN;
self.service_id.serialize(&mut out[start..end]);
start = end;
end = end + Self::NONCE_LEN;
out[start..end].copy_from_slice(&self.service_key);
}
}

View File

@ -17,6 +17,20 @@ impl ConnectionID {
pub fn is_handshake(&self) -> bool { pub fn is_handshake(&self) -> bool {
*self == ConnectionID::Handshake *self == ConnectionID::Handshake
} }
/// length if the connection ID in bytes
pub const fn len() -> usize {
8
}
/// write the ID to a buffer
pub fn serialize(&self, out: &mut [u8]) {
assert!(out.len() == 8, "Insufficient buffer");
match self {
ConnectionID::Handshake => out[..].copy_from_slice(&[0; 8]),
ConnectionID::ID(id) => {
out[..].copy_from_slice(&id.get().to_le_bytes())
}
}
}
} }
impl From<u64> for ConnectionID { impl From<u64> for ConnectionID {

View File

@ -10,6 +10,13 @@ use crate::enc::sym::Secret;
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct KeyID(pub u16); pub struct KeyID(pub u16);
impl KeyID {
/// Length of the Key ID in bytes
pub const fn len() -> usize {
2
}
}
/// Kind of key used in the handshake /// Kind of key used in the handshake
#[derive(Debug, Copy, Clone, PartialEq, ::num_derive::FromPrimitive)] #[derive(Debug, Copy, Clone, PartialEq, ::num_derive::FromPrimitive)]
#[repr(u8)] #[repr(u8)]

View File

@ -19,4 +19,7 @@ pub enum Error {
/// Unsupported cipher /// Unsupported cipher
#[error("unsupported cipher")] #[error("unsupported cipher")]
UnsupportedCipher, UnsupportedCipher,
/// Can not decrypt. Either corrupted or malicious data
#[error("decrypt: corrupted data")]
Decrypt,
} }

View File

@ -1,5 +1,7 @@
//! Symmetric cypher stuff //! Symmetric cypher stuff
use super::Error;
use ::std::collections::VecDeque;
use ::zeroize::Zeroize; use ::zeroize::Zeroize;
/// Secret, used for keys. /// Secret, used for keys.
@ -106,14 +108,21 @@ impl Cipher {
} }
} }
} }
fn decrypt(&self, aad: AAD, data: &mut [u8]) -> Result<(), ()> { fn decrypt<'a>(
&self,
aad: AAD,
data: &mut VecDeque<u8>,
) -> Result<(), Error> {
match self { match self {
Cipher::XChaCha20Poly1305(cipher) => { Cipher::XChaCha20Poly1305(cipher) => {
use ::chacha20poly1305::{ use ::chacha20poly1305::{
aead::generic_array::GenericArray, AeadInPlace, aead::generic_array::GenericArray, AeadInPlace,
}; };
let final_len: usize;
{
let raw_data = data.as_mut_slices().0;
// FIXME: check min data length // FIXME: check min data length
let (nonce_bytes, data_and_tag) = data.split_at_mut(13); let (nonce_bytes, data_and_tag) = raw_data.split_at_mut(13);
let (data_notag, tag_bytes) = data_and_tag.split_at_mut( let (data_notag, tag_bytes) = data_and_tag.split_at_mut(
data_and_tag.len() + 1 data_and_tag.len() + 1
- ::ring::aead::CHACHA20_POLY1305.tag_len(), - ::ring::aead::CHACHA20_POLY1305.tag_len(),
@ -127,10 +136,13 @@ impl Cipher {
tag, tag,
); );
if maybe.is_err() { if maybe.is_err() {
Err(()) return Err(Error::Decrypt);
} else {
Ok(())
} }
final_len = data_notag.len();
}
data.drain(..Nonce::len());
data.truncate(final_len);
Ok(())
} }
} }
} }
@ -151,7 +163,11 @@ impl CipherRecv {
} }
/// Decrypt a paket. Nonce and Tag are taken from the packet, /// Decrypt a paket. Nonce and Tag are taken from the packet,
/// while you need to provide AAD (Additional Authenticated Data) /// while you need to provide AAD (Additional Authenticated Data)
pub fn decrypt(&self, aad: AAD, data: &mut [u8]) -> Result<(), ()> { pub fn decrypt<'a>(
&self,
aad: AAD,
data: &mut VecDeque<u8>,
) -> Result<(), Error> {
self.0.decrypt(aad, data) self.0.decrypt(aad, data)
} }
} }
@ -236,6 +252,10 @@ impl Nonce {
} }
} }
} }
/// Length of this nonce in bytes
pub fn len() -> usize {
return 12;
}
/// Get reference to the nonce bytes /// Get reference to the nonce bytes
pub fn as_bytes(&self) -> &[u8] { pub fn as_bytes(&self) -> &[u8] {
#[allow(unsafe_code)] #[allow(unsafe_code)]

View File

@ -13,13 +13,15 @@
//! //!
//! libFenrir is the official rust library implementing the Fenrir protocol //! libFenrir is the official rust library implementing the Fenrir protocol
pub mod auth;
mod config; mod config;
pub mod connection; pub mod connection;
pub mod dnssec; pub mod dnssec;
pub mod enc; pub mod enc;
use ::arc_swap::{ArcSwap, ArcSwapAny}; use ::arc_swap::{ArcSwap, ArcSwapAny, ArcSwapOption};
use ::std::{net::SocketAddr, sync::Arc}; use ::std::{net::SocketAddr, pin::Pin, sync::Arc};
use ::tokio::macros::support::Future;
use ::tokio::{net::UdpSocket, task::JoinHandle}; use ::tokio::{net::UdpSocket, task::JoinHandle};
use crate::enc::{ use crate::enc::{
@ -50,18 +52,35 @@ pub enum Error {
Key(#[from] crate::enc::Error), Key(#[from] crate::enc::Error),
} }
// No async here
struct FenrirInner { struct FenrirInner {
key_exchanges: ArcSwapAny<Arc<Vec<(asym::Key, asym::KeyExchange)>>>, key_exchanges: ArcSwapAny<Arc<Vec<(asym::Key, asym::KeyExchange)>>>,
ciphers: ArcSwapAny<Arc<Vec<CipherKind>>>, ciphers: ArcSwapAny<Arc<Vec<CipherKind>>>,
keys: ArcSwapAny<Arc<Vec<HandshakeKey>>>, keys: ArcSwapAny<Arc<Vec<HandshakeKey>>>,
} }
/// Intermediate actions to be taken while parsing the handshake
#[derive(Debug, Clone)]
pub enum HandshakeAction {
/// Parsing finished, all ok, nothing to do
None,
/// Packet parsed, now go perform authentication
AuthNeeded(Handshake),
}
// No async here
impl FenrirInner { impl FenrirInner {
fn recv_handshake(&self, handshake: Handshake) -> Result<(), Error> { fn recv_handshake(
use connection::handshake::{dirsync::DirSync, HandshakeData}; &self,
mut handshake: Handshake,
) -> Result<HandshakeAction, Error> {
use connection::handshake::{
dirsync::{self, DirSync},
HandshakeData,
};
match handshake.data { match handshake.data {
HandshakeData::DirSync(ds) => match ds { HandshakeData::DirSync(ref mut ds) => match ds {
DirSync::Req(mut req) => { DirSync::Req(ref mut req) => {
let ephemeral_key = { let ephemeral_key = {
// Keep this block short to avoid contention // Keep this block short to avoid contention
// on self.keys // on self.keys
@ -70,7 +89,7 @@ impl FenrirInner {
keys.iter().find(|k| k.id == req.key_id) keys.iter().find(|k| k.id == req.key_id)
{ {
use enc::asym::PrivKey; use enc::asym::PrivKey;
// Directory synchronized can only used keys // Directory synchronized can only use keys
// for key exchange, not signing keys // for key exchange, not signing keys
if let PrivKey::Exchange(k) = &h_k.key { if let PrivKey::Exchange(k) = &h_k.key {
Some(k.clone()) Some(k.clone())
@ -115,9 +134,15 @@ impl FenrirInner {
let cipher_recv = CipherRecv::new(req.cipher, secret_recv); let cipher_recv = CipherRecv::new(req.cipher, secret_recv);
use crate::enc::sym::AAD; use crate::enc::sym::AAD;
let aad = AAD(&mut []); // no aad for now let aad = AAD(&mut []); // no aad for now
let _ = cipher_recv.decrypt(aad, &mut req.enc); match cipher_recv.decrypt(aad, &mut req.data.ciphertext()) {
Ok(()) => req.data.mark_as_cleartext(),
Err(e) => {
return Err(handshake::Error::Key(e).into());
}
}
req.set_data(dirsync::ReqData::parse(&req.data)?);
todo!(); return Ok(HandshakeAction::AuthNeeded(handshake));
} }
DirSync::Resp(resp) => { DirSync::Resp(resp) => {
todo!(); todo!();
@ -127,6 +152,12 @@ impl FenrirInner {
} }
} }
type TokenChecker =
fn(
user: auth::UserID,
token: auth::Token,
) -> ::futures::future::BoxFuture<'static, Result<bool, ()>>;
/// Instance of a fenrir endpoint /// Instance of a fenrir endpoint
#[allow(missing_copy_implementations, missing_debug_implementations)] #[allow(missing_copy_implementations, missing_debug_implementations)]
pub struct Fenrir { pub struct Fenrir {
@ -140,6 +171,8 @@ pub struct Fenrir {
stop_working: ::tokio::sync::broadcast::Sender<bool>, stop_working: ::tokio::sync::broadcast::Sender<bool>,
/// Private keys used in the handshake /// Private keys used in the handshake
_inner: Arc<FenrirInner>, _inner: Arc<FenrirInner>,
/// where to ask for token check
token_check: Arc<ArcSwapOption<TokenChecker>>,
} }
// TODO: graceful vs immediate stop // TODO: graceful vs immediate stop
@ -165,6 +198,7 @@ impl Fenrir {
key_exchanges: ArcSwapAny::new(Arc::new(Vec::new())), key_exchanges: ArcSwapAny::new(Arc::new(Vec::new())),
keys: ArcSwapAny::new(Arc::new(Vec::new())), keys: ArcSwapAny::new(Arc::new(Vec::new())),
}), }),
token_check: Arc::new(ArcSwapOption::from(None)),
}; };
Ok(endpoint) Ok(endpoint)
} }
@ -259,6 +293,7 @@ impl Fenrir {
async fn listen_udp( async fn listen_udp(
mut stop_working: ::tokio::sync::broadcast::Receiver<bool>, mut stop_working: ::tokio::sync::broadcast::Receiver<bool>,
fenrir: Arc<FenrirInner>, fenrir: Arc<FenrirInner>,
token_check: Arc<ArcSwapOption<TokenChecker>>,
socket: Arc<UdpSocket>, socket: Arc<UdpSocket>,
) -> ::std::io::Result<()> { ) -> ::std::io::Result<()> {
// jumbo frames are 9K max // jumbo frames are 9K max
@ -272,7 +307,13 @@ impl Fenrir {
result? result?
} }
}; };
Self::recv(fenrir.clone(), &buffer[0..bytes], sock_from).await; Self::recv(
fenrir.clone(),
token_check.clone(),
&buffer[0..bytes],
sock_from,
)
.await;
} }
Ok(()) Ok(())
} }
@ -293,6 +334,7 @@ impl Fenrir {
let join = ::tokio::spawn(Self::listen_udp( let join = ::tokio::spawn(Self::listen_udp(
stop_working, stop_working,
self._inner.clone(), self._inner.clone(),
self.token_check.clone(),
s.clone(), s.clone(),
)); ));
self.sockets.push((s, join)); self.sockets.push((s, join));
@ -323,6 +365,7 @@ impl Fenrir {
/// Read and do stuff with the udp packet /// Read and do stuff with the udp packet
async fn recv( async fn recv(
fenrir: Arc<FenrirInner>, fenrir: Arc<FenrirInner>,
token_check: Arc<ArcSwapOption<TokenChecker>>,
buffer: &[u8], buffer: &[u8],
_sock_from: SocketAddr, _sock_from: SocketAddr,
) { ) {
@ -340,10 +383,52 @@ impl Fenrir {
return; return;
} }
}; };
if let Err(err) = fenrir.recv_handshake(handshake) { let action = match fenrir.recv_handshake(handshake) {
Ok(action) => action,
Err(err) => {
::tracing::debug!("Handshake recv error {}", err); ::tracing::debug!("Handshake recv error {}", err);
return; return;
} }
};
match action {
HandshakeAction::AuthNeeded(hshake) => {
let tk_check = match token_check.load_full() {
Some(tokenchecker) => tokenchecker,
None => {
::tracing::error!(
"Handshake received, but no tocken_checker"
);
return;
}
};
use handshake::{
dirsync::{self, DirSync},
HandshakeData,
};
match hshake.data {
HandshakeData::DirSync(ds) => match ds {
DirSync::Req(req) => {
use dirsync::ReqInner;
let req_data = match req.data {
ReqInner::Data(req_data) => req_data,
_ => {
::tracing::error!(
"token_check: expected Data"
);
return;
}
};
tk_check(req_data.user, req_data.token).await;
todo!()
}
_ => {
todo!()
}
},
}
}
_ => {}
};
} }
// copy packet, spawn // copy packet, spawn
todo!(); todo!();