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
use ::ring::rand::SecureRandom;
use ::zeroize::Zeroize;
/// User identifier. 16 bytes for easy uuid conversion
#[derive(Debug, Copy, Clone)]
pub struct UserID([u8; 16]);
@ -11,13 +14,20 @@ impl From<[u8; 16]> for 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
pub const fn len() -> usize {
16
}
}
/// Authentication Token, basically just 32 random bytes
#[derive(Copy, Clone)]
#[derive(Clone, Zeroize)]
#[zeroize(drop)]
pub struct Token([u8; 32]);
impl Token {
@ -42,3 +52,45 @@ impl ::core::fmt::Debug for Token {
::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 {
fn parse(raw: &[u8]) -> Result<HandshakeData, Error> {
fn deserialize(raw: &[u8]) -> Result<HandshakeData, Error> {
const MIN_PKT_LEN: usize = 10;
if raw.len() < MIN_PKT_LEN {
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
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct ReqData {
/// Random nonce, the client can use this to track multiple key exchanges
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: auth::Token,
/// Receiving connection id for the client
pub id: ID,
// TODO: service info
/// Authentication data
pub auth: AuthInfo,
}
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
pub fn parse(raw: &ReqInner) -> Result<Self, Error> {
const MIN_PKT_LEN: usize = 16
+ KeyID::len()
+ auth::UserID::len()
+ auth::Token::len()
+ ID::len();
pub fn deserialize(raw: &ReqInner) -> Result<Self, Error> {
let raw = match raw {
// raw is VecDeque, assume everything is on the first slice
ReqInner::Cleartext(raw) => raw.as_slices().0,
_ => return Err(Error::Parsing),
};
if raw.len() < MIN_PKT_LEN {
if raw.len() < Self::MIN_PKT_LEN {
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 =
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();
KeyID(u16::from_le_bytes(raw[start..end].try_into().unwrap()));
start = end;
end = end + ID::len();
let id: ID =
u64::from_le_bytes(raw[start..end].try_into().unwrap()).into();
if id.is_handshake() {
return Err(Error::Parsing);
}
start = end;
//end = raw.len();
let auth = AuthInfo::deserialize(&raw[start..])?;
Ok(Self {
nonce,
client_key_id,
user,
token,
id,
auth,
})
}
}
@ -180,7 +244,7 @@ pub struct Resp {
}
impl super::HandshakeParsing for Resp {
fn parse(raw: &[u8]) -> Result<HandshakeData, Error> {
fn deserialize(raw: &[u8]) -> Result<HandshakeData, Error> {
todo!()
}
}

View File

@ -68,7 +68,7 @@ pub struct Handshake {
impl Handshake {
const MIN_PKT_LEN: usize = 8;
/// 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 {
return Err(Error::NotEnoughData);
}
@ -81,8 +81,8 @@ impl Handshake {
None => return Err(Error::Parsing),
};
let data = match handshake_kind {
Kind::DirSyncReq => dirsync::Req::parse(&raw[2..])?,
Kind::DirSyncResp => dirsync::Resp::parse(&raw[2..])?,
Kind::DirSyncReq => dirsync::Req::deserialize(&raw[2..])?,
Kind::DirSyncResp => dirsync::Resp::deserialize(&raw[2..])?,
};
Ok(Self {
fenrir_version,
@ -95,5 +95,5 @@ impl Handshake {
}
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 {
/// 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
pub fn is_handshake(&self) -> bool {
*self == ConnectionID::Handshake
@ -23,7 +37,7 @@ impl ConnectionID {
}
/// write the ID to a buffer
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 {
ConnectionID::Handshake => out[..].copy_from_slice(&[0; 8]),
ConnectionID::ID(id) => {

View File

@ -9,30 +9,31 @@ use ::zeroize::Zeroize;
#[derive(Zeroize)]
#[zeroize(drop)]
#[allow(missing_debug_implementations)]
pub struct Secret {
secret: [u8; 32],
}
pub struct Secret([u8; 32]);
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
pub fn as_ref(&self) -> &[u8; 32] {
&self.secret
&self.0
}
}
impl From<[u8; 32]> for Secret {
fn from(shared_secret: [u8; 32]) -> Self {
Self {
secret: shared_secret,
}
Self(shared_secret)
}
}
impl From<::x25519_dalek::SharedSecret> for Secret {
fn from(shared_secret: ::x25519_dalek::SharedSecret) -> Self {
Self {
secret: shared_secret.to_bytes(),
}
Self(shared_secret.to_bytes())
}
}
@ -234,7 +235,7 @@ impl ::core::fmt::Debug for Nonce {
) -> Result<(), ::std::fmt::Error> {
#[allow(unsafe_code)]
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
struct FenrirInner {
// PERF: rand uses syscalls. should we do that async?
rand: ::ring::rand::SystemRandom,
key_exchanges: ArcSwapAny<Arc<Vec<(asym::Key, asym::KeyExchange)>>>,
ciphers: ArcSwapAny<Arc<Vec<CipherKind>>>,
keys: ArcSwapAny<Arc<Vec<HandshakeKey>>>,
@ -140,7 +142,7 @@ impl FenrirInner {
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));
}
@ -156,6 +158,8 @@ type TokenChecker =
fn(
user: auth::UserID,
token: auth::Token,
service_id: auth::ServiceID,
domain: auth::Domain,
) -> ::futures::future::BoxFuture<'static, Result<bool, ()>>;
/// Instance of a fenrir endpoint
@ -194,6 +198,7 @@ impl Fenrir {
dnssec: None,
stop_working: sender,
_inner: Arc::new(FenrirInner {
rand: ::ring::rand::SystemRandom::new(),
ciphers: ArcSwapAny::new(Arc::new(Vec::new())),
key_exchanges: 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");
if ID::from(raw_id).is_handshake() {
use connection::handshake::Handshake;
let handshake = match Handshake::parse(&buffer[8..]) {
let handshake = match Handshake::deserialize(&buffer[8..]) {
Ok(handshake) => handshake,
Err(e) => {
::tracing::warn!("Handshake parsing: {}", e);
@ -393,7 +398,7 @@ impl Fenrir {
match action {
HandshakeAction::AuthNeeded(hshake) => {
let tk_check = match token_check.load_full() {
Some(tokenchecker) => tokenchecker,
Some(tk_check) => tk_check,
None => {
::tracing::error!(
"Handshake received, but no tocken_checker"
@ -418,7 +423,41 @@ impl Fenrir {
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!()
}
_ => {