token check function stubs
Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
parent
bb348f392e
commit
f5a605867e
14
flake.nix
14
flake.nix
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
107
src/lib.rs
107
src/lib.rs
|
@ -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!();
|
||||
|
|
Loading…
Reference in New Issue