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
#(rust-bin.stable.latest.default.override {
# go with nightly to have async fn in traits
(rust-bin.nightly."2023-02-01".default.override {
#extensions = [ "rust-src" ];
#targets = [ "arm-unknown-linux-gnueabihf" ];
})
#(rust-bin.nightly."2023-02-01".default.override {
# #extensions = [ "rust-src" ];
# #targets = [ "arm-unknown-linux-gnueabihf" ];
#})
clippy
lld
cargo-watch
rustfmt
cargo-license
lld
rust-bin.stable.latest.default
rustfmt
rust-analyzer
];
shellHook = ''
# 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 crate::{
auth,
connection::ID,
enc::{
asym::{ExchangePubKey, KeyExchange, KeyID},
sym::CipherKind,
},
};
use ::std::vec::Vec;
use ::std::{collections::VecDeque, num::NonZeroU64, vec::Vec};
type Nonce = [u8; 16];
/// Parsed handshake
#[derive(Debug, Clone)]
@ -39,7 +42,14 @@ pub struct Req {
/// Client ephemeral public key used for key exchanges
pub exchange_key: ExchangePubKey,
/// 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 {
@ -63,30 +73,102 @@ impl super::HandshakeParsing for Req {
Ok(exchange_key) => exchange_key,
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 {
key_id,
exchange,
cipher,
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
#[derive(Debug, Clone, Copy)]
pub struct ReqData {
/// 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
pub client_key_id: KeyID,
/// User of the domain
pub user: auth::UserID,
/// Authentication token
pub token: [u8; 32],
pub token: auth::Token,
/// Receiving connection id for the client
pub id: ID,
// 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
#[derive(Debug, Clone)]
@ -107,7 +189,7 @@ impl super::HandshakeParsing for Resp {
#[derive(Debug, Clone, Copy)]
pub struct RespData {
/// Client nonce, copied from the request
client_nonce: [u8; 16],
client_nonce: Nonce,
/// Server Connection ID
id: ID,
/// Service Connection ID
@ -115,3 +197,27 @@ pub struct RespData {
/// Service encryption key
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 {
*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 {

View File

@ -10,6 +10,13 @@ use crate::enc::sym::Secret;
#[derive(Debug, Copy, Clone, PartialEq)]
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
#[derive(Debug, Copy, Clone, PartialEq, ::num_derive::FromPrimitive)]
#[repr(u8)]

View File

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

View File

@ -1,5 +1,7 @@
//! Symmetric cypher stuff
use super::Error;
use ::std::collections::VecDeque;
use ::zeroize::Zeroize;
/// 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 {
Cipher::XChaCha20Poly1305(cipher) => {
use ::chacha20poly1305::{
aead::generic_array::GenericArray, AeadInPlace,
};
let final_len: usize;
{
let raw_data = data.as_mut_slices().0;
// 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(
data_and_tag.len() + 1
- ::ring::aead::CHACHA20_POLY1305.tag_len(),
@ -127,10 +136,13 @@ impl Cipher {
tag,
);
if maybe.is_err() {
Err(())
} else {
Ok(())
return Err(Error::Decrypt);
}
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,
/// 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)
}
}
@ -236,6 +252,10 @@ impl Nonce {
}
}
}
/// Length of this nonce in bytes
pub fn len() -> usize {
return 12;
}
/// Get reference to the nonce bytes
pub fn as_bytes(&self) -> &[u8] {
#[allow(unsafe_code)]

View File

@ -13,13 +13,15 @@
//!
//! libFenrir is the official rust library implementing the Fenrir protocol
pub mod auth;
mod config;
pub mod connection;
pub mod dnssec;
pub mod enc;
use ::arc_swap::{ArcSwap, ArcSwapAny};
use ::std::{net::SocketAddr, sync::Arc};
use ::arc_swap::{ArcSwap, ArcSwapAny, ArcSwapOption};
use ::std::{net::SocketAddr, pin::Pin, sync::Arc};
use ::tokio::macros::support::Future;
use ::tokio::{net::UdpSocket, task::JoinHandle};
use crate::enc::{
@ -50,18 +52,35 @@ pub enum Error {
Key(#[from] crate::enc::Error),
}
// No async here
struct FenrirInner {
key_exchanges: ArcSwapAny<Arc<Vec<(asym::Key, asym::KeyExchange)>>>,
ciphers: ArcSwapAny<Arc<Vec<CipherKind>>>,
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 {
fn recv_handshake(&self, handshake: Handshake) -> Result<(), Error> {
use connection::handshake::{dirsync::DirSync, HandshakeData};
fn recv_handshake(
&self,
mut handshake: Handshake,
) -> Result<HandshakeAction, Error> {
use connection::handshake::{
dirsync::{self, DirSync},
HandshakeData,
};
match handshake.data {
HandshakeData::DirSync(ds) => match ds {
DirSync::Req(mut req) => {
HandshakeData::DirSync(ref mut ds) => match ds {
DirSync::Req(ref mut req) => {
let ephemeral_key = {
// Keep this block short to avoid contention
// on self.keys
@ -70,7 +89,7 @@ impl FenrirInner {
keys.iter().find(|k| k.id == req.key_id)
{
use enc::asym::PrivKey;
// Directory synchronized can only used keys
// Directory synchronized can only use keys
// for key exchange, not signing keys
if let PrivKey::Exchange(k) = &h_k.key {
Some(k.clone())
@ -115,9 +134,15 @@ impl FenrirInner {
let cipher_recv = CipherRecv::new(req.cipher, secret_recv);
use crate::enc::sym::AAD;
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) => {
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
#[allow(missing_copy_implementations, missing_debug_implementations)]
pub struct Fenrir {
@ -140,6 +171,8 @@ pub struct Fenrir {
stop_working: ::tokio::sync::broadcast::Sender<bool>,
/// Private keys used in the handshake
_inner: Arc<FenrirInner>,
/// where to ask for token check
token_check: Arc<ArcSwapOption<TokenChecker>>,
}
// TODO: graceful vs immediate stop
@ -165,6 +198,7 @@ impl Fenrir {
key_exchanges: ArcSwapAny::new(Arc::new(Vec::new())),
keys: ArcSwapAny::new(Arc::new(Vec::new())),
}),
token_check: Arc::new(ArcSwapOption::from(None)),
};
Ok(endpoint)
}
@ -259,6 +293,7 @@ impl Fenrir {
async fn listen_udp(
mut stop_working: ::tokio::sync::broadcast::Receiver<bool>,
fenrir: Arc<FenrirInner>,
token_check: Arc<ArcSwapOption<TokenChecker>>,
socket: Arc<UdpSocket>,
) -> ::std::io::Result<()> {
// jumbo frames are 9K max
@ -272,7 +307,13 @@ impl Fenrir {
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(())
}
@ -293,6 +334,7 @@ impl Fenrir {
let join = ::tokio::spawn(Self::listen_udp(
stop_working,
self._inner.clone(),
self.token_check.clone(),
s.clone(),
));
self.sockets.push((s, join));
@ -323,6 +365,7 @@ impl Fenrir {
/// Read and do stuff with the udp packet
async fn recv(
fenrir: Arc<FenrirInner>,
token_check: Arc<ArcSwapOption<TokenChecker>>,
buffer: &[u8],
_sock_from: SocketAddr,
) {
@ -340,10 +383,52 @@ impl Fenrir {
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);
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
todo!();