More work on authentication

Still lots of unfinished stuff

Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
Luca Fulchir 2023-02-22 12:30:00 +01:00
parent f5a605867e
commit 9e1312b149
Signed by: luca.fulchir
GPG Key ID: 8F6440603D13A78E
6 changed files with 215 additions and 45 deletions

View File

@ -1,5 +1,8 @@
//! Authentication reslated struct definitions //! Authentication reslated struct definitions
use ::ring::rand::SecureRandom;
use ::zeroize::Zeroize;
/// User identifier. 16 bytes for easy uuid conversion /// User identifier. 16 bytes for easy uuid conversion
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct UserID([u8; 16]); pub struct UserID([u8; 16]);
@ -11,13 +14,20 @@ impl From<[u8; 16]> for UserID {
} }
impl UserID { impl UserID {
/// New random user id
pub fn new(rand: &::ring::rand::SystemRandom) -> Self {
let mut ret = Self([0; 16]);
rand.fill(&mut ret.0);
ret
}
/// length of the User ID in bytes /// length of the User ID in bytes
pub const fn len() -> usize { pub const fn len() -> usize {
16 16
} }
} }
/// Authentication Token, basically just 32 random bytes /// Authentication Token, basically just 32 random bytes
#[derive(Copy, Clone)] #[derive(Clone, Zeroize)]
#[zeroize(drop)]
pub struct Token([u8; 32]); pub struct Token([u8; 32]);
impl Token { impl Token {
@ -42,3 +52,45 @@ impl ::core::fmt::Debug for Token {
::core::fmt::Debug::fmt("[hidden token]", f) ::core::fmt::Debug::fmt("[hidden token]", f)
} }
} }
/// domain representation
/// Security notice: internal representation is utf8, but we will
/// further limit to a "safe" subset of utf8
// SECURITY: TODO: limit to a subset of utf8
#[derive(Debug, Clone, PartialEq)]
pub struct Domain(String);
impl TryFrom<&[u8]> for Domain {
type Error = ();
fn try_from(raw: &[u8]) -> Result<Self, ()> {
let domain_string = match String::from_utf8(raw.into()) {
Ok(domain_string) => domain_string,
Err(_) => return Err(()),
};
Ok(Domain(domain_string))
}
}
impl Domain {
/// length of the User ID in bytes
pub fn len(&self) -> usize {
self.0.len()
}
}
/// The Service ID is a UUID associated with the service.
#[derive(Debug, Copy, Clone)]
pub struct ServiceID([u8; 16]);
impl From<[u8; 16]> for ServiceID {
fn from(raw: [u8; 16]) -> Self {
ServiceID(raw)
}
}
impl ServiceID {
/// length of the User ID in bytes
pub const fn len() -> usize {
16
}
}

View File

@ -53,7 +53,7 @@ impl Req {
} }
impl super::HandshakeParsing for Req { impl super::HandshakeParsing for Req {
fn parse(raw: &[u8]) -> Result<HandshakeData, Error> { fn deserialize(raw: &[u8]) -> Result<HandshakeData, Error> {
const MIN_PKT_LEN: usize = 10; const MIN_PKT_LEN: usize = 10;
if raw.len() < MIN_PKT_LEN { if raw.len() < MIN_PKT_LEN {
return Err(Error::NotEnoughData); return Err(Error::NotEnoughData);
@ -119,53 +119,117 @@ impl ReqInner {
} }
} }
/// Informations needed for authentication
#[derive(Debug, Clone)]
pub struct AuthInfo {
/// User of the domain
pub user: auth::UserID,
/// Authentication token
pub token: auth::Token,
/// service ID that we want to use on the domain
pub service_id: auth::ServiceID,
/// subdomain on which we authenticate
// SECURITY: TODO: this should be padded to multiples of 32 bytes or so
// reason: packet length can let you infer the authentication domain
pub domain: auth::Domain,
}
impl AuthInfo {
/// Minimum length of the authentication info
pub const MIN_PKT_LEN: usize =
auth::UserID::len() + auth::Token::len() + auth::ServiceID::len() + 1;
/// Actual length of the authentication info
pub fn len(&self) -> usize {
Self::MIN_PKT_LEN + self.domain.len()
}
/// deserialize from raw bytes
pub fn deserialize(raw: &[u8]) -> Result<Self, Error> {
if raw.len() < Self::MIN_PKT_LEN {
return Err(Error::NotEnoughData);
}
let raw_user_id: [u8; auth::UserID::len()];
let raw_token: [u8; auth::Token::len()];
let raw_service_id: [u8; auth::ServiceID::len()];
let mut start = 0;
let mut end = auth::UserID::len();
raw_user_id = raw[start..end].try_into().unwrap();
let user: auth::UserID = raw_user_id.into();
start = end;
end = end + auth::Token::len();
raw_token = raw[start..end].try_into().unwrap();
let token: auth::Token = raw_token.into();
start = end;
end = end + auth::ServiceID::len();
raw_service_id = raw[start..end].try_into().unwrap();
let service_id: auth::ServiceID = raw_service_id.into();
start = end;
let domain_len = raw[start];
start = start + 1;
end = start + domain_len as usize;
if raw.len() < end {
return Err(Error::NotEnoughData);
}
let domain: auth::Domain = match raw[start..end].try_into() {
Ok(domain) => domain,
Err(_) => return Err(Error::Parsing),
};
Ok(Self {
user,
token,
service_id,
domain,
})
}
}
/// Decrypted request data /// Decrypted request data
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
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: Nonce, 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
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 /// Authentication data
pub auth: AuthInfo,
} }
impl ReqData { impl ReqData {
/// Minimum byte length of the request data
pub const MIN_PKT_LEN: usize =
16 + KeyID::len() + ID::len() + AuthInfo::MIN_PKT_LEN;
/// Parse the cleartext raw data /// Parse the cleartext raw data
pub fn parse(raw: &ReqInner) -> Result<Self, Error> { pub fn deserialize(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 { let raw = match raw {
// raw is VecDeque, assume everything is on the first slice
ReqInner::Cleartext(raw) => raw.as_slices().0, ReqInner::Cleartext(raw) => raw.as_slices().0,
_ => return Err(Error::Parsing), _ => return Err(Error::Parsing),
}; };
if raw.len() < MIN_PKT_LEN { if raw.len() < Self::MIN_PKT_LEN {
return Err(Error::NotEnoughData); return Err(Error::NotEnoughData);
} }
let nonce: Nonce = raw.try_into().unwrap(); let mut start = 0;
let mut end = 16;
let nonce: Nonce = raw[start..end].try_into().unwrap();
start = end;
end = end + KeyID::len();
let client_key_id = let client_key_id =
KeyID(u16::from_le_bytes(raw[16..17].try_into().unwrap())); KeyID(u16::from_le_bytes(raw[start..end].try_into().unwrap()));
let raw_user: [u8; 16] = raw[18..34].try_into().unwrap(); start = end;
let user: auth::UserID = raw_user.into(); end = end + ID::len();
let raw_token: [u8; 32] = raw[34..66].try_into().unwrap(); let id: ID =
let token: auth::Token = raw_token.into(); u64::from_le_bytes(raw[start..end].try_into().unwrap()).into();
let id: ID = u64::from_le_bytes(raw[66..74].try_into().unwrap()).into();
if id.is_handshake() { if id.is_handshake() {
return Err(Error::Parsing); return Err(Error::Parsing);
} }
start = end;
//end = raw.len();
let auth = AuthInfo::deserialize(&raw[start..])?;
Ok(Self { Ok(Self {
nonce, nonce,
client_key_id, client_key_id,
user,
token,
id, id,
auth,
}) })
} }
} }
@ -180,7 +244,7 @@ pub struct Resp {
} }
impl super::HandshakeParsing for Resp { impl super::HandshakeParsing for Resp {
fn parse(raw: &[u8]) -> Result<HandshakeData, Error> { fn deserialize(raw: &[u8]) -> Result<HandshakeData, Error> {
todo!() todo!()
} }
} }

View File

@ -68,7 +68,7 @@ pub struct Handshake {
impl Handshake { impl Handshake {
const MIN_PKT_LEN: usize = 8; const MIN_PKT_LEN: usize = 8;
/// Parse the packet and return the parsed handshake /// Parse the packet and return the parsed handshake
pub fn parse(raw: &[u8]) -> Result<Self, Error> { pub fn deserialize(raw: &[u8]) -> Result<Self, Error> {
if raw.len() < Self::MIN_PKT_LEN { if raw.len() < Self::MIN_PKT_LEN {
return Err(Error::NotEnoughData); return Err(Error::NotEnoughData);
} }
@ -81,8 +81,8 @@ impl Handshake {
None => return Err(Error::Parsing), None => return Err(Error::Parsing),
}; };
let data = match handshake_kind { let data = match handshake_kind {
Kind::DirSyncReq => dirsync::Req::parse(&raw[2..])?, Kind::DirSyncReq => dirsync::Req::deserialize(&raw[2..])?,
Kind::DirSyncResp => dirsync::Resp::parse(&raw[2..])?, Kind::DirSyncResp => dirsync::Resp::deserialize(&raw[2..])?,
}; };
Ok(Self { Ok(Self {
fenrir_version, fenrir_version,
@ -95,5 +95,5 @@ impl Handshake {
} }
trait HandshakeParsing { trait HandshakeParsing {
fn parse(raw: &[u8]) -> Result<HandshakeData, Error>; fn deserialize(raw: &[u8]) -> Result<HandshakeData, Error>;
} }

View File

@ -13,6 +13,20 @@ pub enum ConnectionID {
} }
impl ConnectionID { impl ConnectionID {
/// New random service ID
pub fn new_rand(rand: &::ring::rand::SystemRandom) -> Self {
use ::ring::rand::SecureRandom;
let mut raw = [0; 8];
let mut num = 0;
while num == 0 {
rand.fill(&mut raw);
num = u64::from_le_bytes(raw);
}
#[allow(unsafe_code)]
unsafe {
ConnectionID::ID(::core::num::NonZeroU64::new_unchecked(num))
}
}
/// Quick check to know if this is an handshake /// Quick check to know if this is an handshake
pub fn is_handshake(&self) -> bool { pub fn is_handshake(&self) -> bool {
*self == ConnectionID::Handshake *self == ConnectionID::Handshake
@ -23,7 +37,7 @@ impl ConnectionID {
} }
/// write the ID to a buffer /// write the ID to a buffer
pub fn serialize(&self, out: &mut [u8]) { pub fn serialize(&self, out: &mut [u8]) {
assert!(out.len() == 8, "Insufficient buffer"); assert!(out.len() == 8, "out buffer must be 8 bytes");
match self { match self {
ConnectionID::Handshake => out[..].copy_from_slice(&[0; 8]), ConnectionID::Handshake => out[..].copy_from_slice(&[0; 8]),
ConnectionID::ID(id) => { ConnectionID::ID(id) => {

View File

@ -9,30 +9,31 @@ use ::zeroize::Zeroize;
#[derive(Zeroize)] #[derive(Zeroize)]
#[zeroize(drop)] #[zeroize(drop)]
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct Secret { pub struct Secret([u8; 32]);
secret: [u8; 32],
}
impl Secret { impl Secret {
/// New randomly generated secret
pub fn new_rand(rand: &::ring::rand::SystemRandom) -> Self {
use ::ring::rand::SecureRandom;
let mut ret = Self([0; 32]);
rand.fill(&mut ret.0);
ret
}
/// return a reference to the secret /// return a reference to the secret
pub fn as_ref(&self) -> &[u8; 32] { pub fn as_ref(&self) -> &[u8; 32] {
&self.secret &self.0
} }
} }
impl From<[u8; 32]> for Secret { impl From<[u8; 32]> for Secret {
fn from(shared_secret: [u8; 32]) -> Self { fn from(shared_secret: [u8; 32]) -> Self {
Self { Self(shared_secret)
secret: shared_secret,
}
} }
} }
impl From<::x25519_dalek::SharedSecret> for Secret { impl From<::x25519_dalek::SharedSecret> for Secret {
fn from(shared_secret: ::x25519_dalek::SharedSecret) -> Self { fn from(shared_secret: ::x25519_dalek::SharedSecret) -> Self {
Self { Self(shared_secret.to_bytes())
secret: shared_secret.to_bytes(),
}
} }
} }
@ -234,7 +235,7 @@ impl ::core::fmt::Debug for Nonce {
) -> Result<(), ::std::fmt::Error> { ) -> Result<(), ::std::fmt::Error> {
#[allow(unsafe_code)] #[allow(unsafe_code)]
unsafe { unsafe {
core::fmt::Debug::fmt(&self.num, f) ::core::fmt::Debug::fmt(&self.num, f)
} }
} }
} }

View File

@ -54,6 +54,8 @@ pub enum Error {
// No async here // No async here
struct FenrirInner { struct FenrirInner {
// PERF: rand uses syscalls. should we do that async?
rand: ::ring::rand::SystemRandom,
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>>>,
@ -140,7 +142,7 @@ impl FenrirInner {
return Err(handshake::Error::Key(e).into()); return Err(handshake::Error::Key(e).into());
} }
} }
req.set_data(dirsync::ReqData::parse(&req.data)?); req.set_data(dirsync::ReqData::deserialize(&req.data)?);
return Ok(HandshakeAction::AuthNeeded(handshake)); return Ok(HandshakeAction::AuthNeeded(handshake));
} }
@ -156,6 +158,8 @@ type TokenChecker =
fn( fn(
user: auth::UserID, user: auth::UserID,
token: auth::Token, token: auth::Token,
service_id: auth::ServiceID,
domain: auth::Domain,
) -> ::futures::future::BoxFuture<'static, Result<bool, ()>>; ) -> ::futures::future::BoxFuture<'static, Result<bool, ()>>;
/// Instance of a fenrir endpoint /// Instance of a fenrir endpoint
@ -194,6 +198,7 @@ impl Fenrir {
dnssec: None, dnssec: None,
stop_working: sender, stop_working: sender,
_inner: Arc::new(FenrirInner { _inner: Arc::new(FenrirInner {
rand: ::ring::rand::SystemRandom::new(),
ciphers: ArcSwapAny::new(Arc::new(Vec::new())), ciphers: ArcSwapAny::new(Arc::new(Vec::new())),
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())),
@ -376,7 +381,7 @@ impl Fenrir {
let raw_id: [u8; 8] = buffer.try_into().expect("unreachable"); let raw_id: [u8; 8] = buffer.try_into().expect("unreachable");
if ID::from(raw_id).is_handshake() { if ID::from(raw_id).is_handshake() {
use connection::handshake::Handshake; use connection::handshake::Handshake;
let handshake = match Handshake::parse(&buffer[8..]) { let handshake = match Handshake::deserialize(&buffer[8..]) {
Ok(handshake) => handshake, Ok(handshake) => handshake,
Err(e) => { Err(e) => {
::tracing::warn!("Handshake parsing: {}", e); ::tracing::warn!("Handshake parsing: {}", e);
@ -393,7 +398,7 @@ impl Fenrir {
match action { match action {
HandshakeAction::AuthNeeded(hshake) => { HandshakeAction::AuthNeeded(hshake) => {
let tk_check = match token_check.load_full() { let tk_check = match token_check.load_full() {
Some(tokenchecker) => tokenchecker, Some(tk_check) => tk_check,
None => { None => {
::tracing::error!( ::tracing::error!(
"Handshake received, but no tocken_checker" "Handshake received, but no tocken_checker"
@ -418,7 +423,41 @@ impl Fenrir {
return; return;
} }
}; };
tk_check(req_data.user, req_data.token).await; let is_authenticated = match tk_check(
req_data.auth.user,
req_data.auth.token,
req_data.auth.service_id,
req_data.auth.domain,
)
.await
{
Ok(is_authenticated) => is_authenticated,
Err(_) => {
::tracing::error!(
"error in token auth"
);
// TODO: retry?
return;
}
};
if !is_authenticated {
::tracing::warn!(
"Wrong authentication for user {:?}",
req_data.auth.user
);
// TODO: error response
return;
}
// Client has correctly authenticated
// TODO: contact the service, get the key and
// connection ID
let srv_conn_id =
connection::ID::new_rand(&fenrir.rand);
let auth_conn_id =
connection::ID::new_rand(&fenrir.rand);
let srv_secret =
enc::sym::Secret::new_rand(&fenrir.rand);
todo!() todo!()
} }
_ => { _ => {