Namespace split the dirsync request/response
There was no big problem, but it was messy Signed-off-by: Luca Fulchir <luca.fulchir@runesauth.com>
This commit is contained in:
parent
bf877cf86e
commit
5dff5c8c9a
|
@ -0,0 +1,77 @@
|
||||||
|
//! Directory synchronized handshake
|
||||||
|
//! 1-RTT connection
|
||||||
|
//!
|
||||||
|
//! The simplest, fastest handshake supported by Fenrir
|
||||||
|
//! Downside: It does not offer protection from DDos,
|
||||||
|
//! no perfect forward secrecy
|
||||||
|
//!
|
||||||
|
//! To grant a form of perfect forward secrecy, the server should periodically
|
||||||
|
//! change the DNSSEC public/private keys
|
||||||
|
|
||||||
|
use crate::enc::{
|
||||||
|
sym::{NonceLen, TagLen},
|
||||||
|
Random,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod req;
|
||||||
|
pub mod resp;
|
||||||
|
|
||||||
|
// TODO: merge with crate::enc::sym::Nonce
|
||||||
|
/// random nonce
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct Nonce(pub(crate) [u8; 16]);
|
||||||
|
|
||||||
|
impl Nonce {
|
||||||
|
/// Create a new random Nonce
|
||||||
|
pub fn new(rnd: &Random) -> Self {
|
||||||
|
use ::core::mem::MaybeUninit;
|
||||||
|
let mut out: MaybeUninit<[u8; 16]>;
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
unsafe {
|
||||||
|
out = MaybeUninit::uninit();
|
||||||
|
let _ = rnd.fill(out.assume_init_mut());
|
||||||
|
Self(out.assume_init())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Length of the serialized Nonce
|
||||||
|
pub const fn len() -> usize {
|
||||||
|
16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&[u8; 16]> for Nonce {
|
||||||
|
fn from(raw: &[u8; 16]) -> Self {
|
||||||
|
Self(raw.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parsed handshake
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum DirSync {
|
||||||
|
/// Directory synchronized handshake: client request
|
||||||
|
Req(req::Req),
|
||||||
|
/// Directory synchronized handshake: server response
|
||||||
|
Resp(resp::Resp),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DirSync {
|
||||||
|
/// actual length of the dirsync handshake data
|
||||||
|
pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize {
|
||||||
|
match self {
|
||||||
|
DirSync::Req(req) => req.len(),
|
||||||
|
DirSync::Resp(resp) => resp.len(head_len, tag_len),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Serialize into raw bytes
|
||||||
|
/// NOTE: assumes that there is exactly asa much buffer as needed
|
||||||
|
pub fn serialize(
|
||||||
|
&self,
|
||||||
|
head_len: NonceLen,
|
||||||
|
tag_len: TagLen,
|
||||||
|
out: &mut [u8],
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
DirSync::Req(req) => req.serialize(head_len, tag_len, out),
|
||||||
|
DirSync::Resp(resp) => resp.serialize(head_len, tag_len, out),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,85 +1,22 @@
|
||||||
//! Directory synchronized handshake
|
//! Directory synchronized handshake, Request parsing
|
||||||
//! 1-RTT connection
|
|
||||||
//!
|
|
||||||
//! The simplest, fastest handshake supported by Fenrir
|
|
||||||
//! Downside: It does not offer protection from DDos,
|
|
||||||
//! no perfect forward secrecy
|
|
||||||
//!
|
|
||||||
//! To grant a form of perfect forward secrecy, the server should periodically
|
|
||||||
//! change the DNSSEC public/private keys
|
|
||||||
|
|
||||||
use super::Error;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth,
|
auth,
|
||||||
connection::{handshake, ProtocolVersion, ID},
|
connection::{
|
||||||
|
handshake::{
|
||||||
|
self,
|
||||||
|
dirsync::{DirSync, Nonce},
|
||||||
|
Error,
|
||||||
|
},
|
||||||
|
ProtocolVersion, ID,
|
||||||
|
},
|
||||||
enc::{
|
enc::{
|
||||||
asym::{ExchangePubKey, KeyExchangeKind, KeyID},
|
asym::{ExchangePubKey, KeyExchangeKind, KeyID},
|
||||||
hkdf,
|
hkdf,
|
||||||
sym::{self, NonceLen, TagLen},
|
sym::{self, NonceLen, TagLen},
|
||||||
Random, Secret,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: merge with crate::enc::sym::Nonce
|
|
||||||
/// random nonce
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub struct Nonce(pub(crate) [u8; 16]);
|
|
||||||
|
|
||||||
impl Nonce {
|
|
||||||
/// Create a new random Nonce
|
|
||||||
pub fn new(rnd: &Random) -> Self {
|
|
||||||
use ::core::mem::MaybeUninit;
|
|
||||||
let mut out: MaybeUninit<[u8; 16]>;
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
unsafe {
|
|
||||||
out = MaybeUninit::uninit();
|
|
||||||
let _ = rnd.fill(out.assume_init_mut());
|
|
||||||
Self(out.assume_init())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Length of the serialized Nonce
|
|
||||||
pub const fn len() -> usize {
|
|
||||||
16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<&[u8; 16]> for Nonce {
|
|
||||||
fn from(raw: &[u8; 16]) -> Self {
|
|
||||||
Self(raw.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parsed handshake
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum DirSync {
|
|
||||||
/// Directory synchronized handshake: client request
|
|
||||||
Req(Req),
|
|
||||||
/// Directory synchronized handshake: server response
|
|
||||||
Resp(Resp),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DirSync {
|
|
||||||
/// actual length of the dirsync handshake data
|
|
||||||
pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize {
|
|
||||||
match self {
|
|
||||||
DirSync::Req(req) => req.len(),
|
|
||||||
DirSync::Resp(resp) => resp.len(head_len, tag_len),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Serialize into raw bytes
|
|
||||||
/// NOTE: assumes that there is exactly asa much buffer as needed
|
|
||||||
pub fn serialize(
|
|
||||||
&self,
|
|
||||||
head_len: NonceLen,
|
|
||||||
tag_len: TagLen,
|
|
||||||
out: &mut [u8],
|
|
||||||
) {
|
|
||||||
match self {
|
|
||||||
DirSync::Req(req) => req.serialize(head_len, tag_len, out),
|
|
||||||
DirSync::Resp(resp) => resp.serialize(head_len, tag_len, out),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Client request of a directory synchronized handshake
|
/// Client request of a directory synchronized handshake
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Req {
|
pub struct Req {
|
||||||
|
@ -94,7 +31,7 @@ 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 data: ReqState,
|
pub data: State,
|
||||||
// SECURITY: TODO: Add padding to min: 1200 bytes
|
// SECURITY: TODO: Add padding to min: 1200 bytes
|
||||||
// to avoid amplification attaks
|
// to avoid amplification attaks
|
||||||
// also: 1200 < 1280 to allow better vpn compatibility
|
// also: 1200 < 1280 to allow better vpn compatibility
|
||||||
|
@ -119,8 +56,8 @@ impl Req {
|
||||||
tag_len: TagLen,
|
tag_len: TagLen,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
match &self.data {
|
match &self.data {
|
||||||
ReqState::ClearText(data) => data.len() + head_len.0 + tag_len.0,
|
State::ClearText(data) => data.len() + head_len.0 + tag_len.0,
|
||||||
ReqState::CipherText(length) => *length,
|
State::CipherText(length) => *length,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// actual length of the directory synchronized request
|
/// actual length of the directory synchronized request
|
||||||
|
@ -150,7 +87,7 @@ impl Req {
|
||||||
let written_next = 5 + key_len;
|
let written_next = 5 + key_len;
|
||||||
self.exchange_key.serialize_into(&mut out[5..written_next]);
|
self.exchange_key.serialize_into(&mut out[5..written_next]);
|
||||||
let written = written_next;
|
let written = written_next;
|
||||||
if let ReqState::ClearText(data) = &self.data {
|
if let State::ClearText(data) = &self.data {
|
||||||
let from = written + head_len.0;
|
let from = written + head_len.0;
|
||||||
let to = out.len() - tag_len.0;
|
let to = out.len() - tag_len.0;
|
||||||
data.serialize(&mut out[from..to]);
|
data.serialize(&mut out[from..to]);
|
||||||
|
@ -190,7 +127,7 @@ impl handshake::Parsing 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 data = ReqState::CipherText(raw.len() - (CURR_SIZE + len));
|
let data = State::CipherText(raw.len() - (CURR_SIZE + len));
|
||||||
Ok(handshake::Data::DirSync(DirSync::Req(Self {
|
Ok(handshake::Data::DirSync(DirSync::Req(Self {
|
||||||
key_id,
|
key_id,
|
||||||
exchange,
|
exchange,
|
||||||
|
@ -204,18 +141,18 @@ impl handshake::Parsing for Req {
|
||||||
|
|
||||||
/// Quick way to avoid mixing cipher and clear text
|
/// Quick way to avoid mixing cipher and clear text
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum ReqState {
|
pub enum State {
|
||||||
/// Data is still encrytped, we only keep the length
|
/// Data is still encrytped, we only keep the length
|
||||||
CipherText(usize),
|
CipherText(usize),
|
||||||
/// Client data, decrypted and parsed
|
/// Client data, decrypted and parsed
|
||||||
ClearText(ReqData),
|
ClearText(Data),
|
||||||
}
|
}
|
||||||
impl ReqState {
|
impl State {
|
||||||
/// The length of the data
|
/// The length of the data
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
ReqState::CipherText(len) => *len,
|
State::CipherText(len) => *len,
|
||||||
ReqState::ClearText(data) => data.len(),
|
State::ClearText(data) => data.len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// parse the cleartext
|
/// parse the cleartext
|
||||||
|
@ -224,19 +161,19 @@ impl ReqState {
|
||||||
raw: &[u8],
|
raw: &[u8],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let clear = match self {
|
let clear = match self {
|
||||||
ReqState::CipherText(len) => {
|
State::CipherText(len) => {
|
||||||
assert!(
|
assert!(
|
||||||
*len > raw.len(),
|
*len > raw.len(),
|
||||||
"DirSync::ReqState::CipherText length mismatch"
|
"DirSync::State::CipherText length mismatch"
|
||||||
);
|
);
|
||||||
match ReqData::deserialize(raw) {
|
match Data::deserialize(raw) {
|
||||||
Ok(clear) => clear,
|
Ok(clear) => clear,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return Err(Error::Parsing),
|
_ => return Err(Error::Parsing),
|
||||||
};
|
};
|
||||||
*self = ReqState::ClearText(clear);
|
*self = State::ClearText(clear);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -321,7 +258,7 @@ impl AuthInfo {
|
||||||
|
|
||||||
/// Decrypted request data
|
/// Decrypted request data
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ReqData {
|
pub struct Data {
|
||||||
/// 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
|
||||||
|
@ -331,7 +268,7 @@ pub struct ReqData {
|
||||||
/// Authentication data
|
/// Authentication data
|
||||||
pub auth: AuthInfo,
|
pub auth: AuthInfo,
|
||||||
}
|
}
|
||||||
impl ReqData {
|
impl Data {
|
||||||
/// actual length of the request data
|
/// actual length of the request data
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
Nonce::len() + KeyID::len() + ID::len() + self.auth.len()
|
Nonce::len() + KeyID::len() + ID::len() + self.auth.len()
|
||||||
|
@ -383,177 +320,3 @@ impl ReqData {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Quick way to avoid mixing cipher and clear text
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum RespState {
|
|
||||||
/// Server data, still in ciphertext
|
|
||||||
CipherText(usize),
|
|
||||||
/// Parsed, cleartext server data
|
|
||||||
ClearText(RespData),
|
|
||||||
}
|
|
||||||
impl RespState {
|
|
||||||
/// The length of the data
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
RespState::CipherText(len) => *len,
|
|
||||||
RespState::ClearText(_) => RespData::len(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// parse the cleartext
|
|
||||||
pub fn deserialize_as_cleartext(
|
|
||||||
&mut self,
|
|
||||||
raw: &[u8],
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let clear = match self {
|
|
||||||
RespState::CipherText(len) => {
|
|
||||||
assert!(
|
|
||||||
*len > raw.len(),
|
|
||||||
"DirSync::RespState::CipherText length mismatch"
|
|
||||||
);
|
|
||||||
match RespData::deserialize(raw) {
|
|
||||||
Ok(clear) => clear,
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return Err(Error::Parsing),
|
|
||||||
};
|
|
||||||
*self = RespState::ClearText(clear);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Serialize the still cleartext data
|
|
||||||
pub fn serialize(&self, out: &mut [u8]) {
|
|
||||||
if let RespState::ClearText(clear) = &self {
|
|
||||||
clear.serialize(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Server response in a directory synchronized handshake
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct Resp {
|
|
||||||
/// Tells the client with which key the exchange was done
|
|
||||||
pub client_key_id: KeyID,
|
|
||||||
/// actual response data, might be encrypted
|
|
||||||
pub data: RespState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl handshake::Parsing for Resp {
|
|
||||||
fn deserialize(raw: &[u8]) -> Result<handshake::Data, Error> {
|
|
||||||
const MIN_PKT_LEN: usize = 68;
|
|
||||||
if raw.len() < MIN_PKT_LEN {
|
|
||||||
return Err(Error::NotEnoughData);
|
|
||||||
}
|
|
||||||
let client_key_id: KeyID =
|
|
||||||
KeyID(u16::from_le_bytes(raw[0..KeyID::len()].try_into().unwrap()));
|
|
||||||
Ok(handshake::Data::DirSync(DirSync::Resp(Self {
|
|
||||||
client_key_id,
|
|
||||||
data: RespState::CipherText(raw[KeyID::len()..].len()),
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resp {
|
|
||||||
/// return the offset of the encrypted data
|
|
||||||
/// NOTE: starts from the beginning of the fenrir packet
|
|
||||||
pub fn encrypted_offset(&self) -> usize {
|
|
||||||
ProtocolVersion::len() + handshake::ID::len() + KeyID::len()
|
|
||||||
}
|
|
||||||
/// return the total length of the cleartext data
|
|
||||||
pub fn encrypted_length(
|
|
||||||
&self,
|
|
||||||
head_len: NonceLen,
|
|
||||||
tag_len: TagLen,
|
|
||||||
) -> usize {
|
|
||||||
match &self.data {
|
|
||||||
RespState::ClearText(_data) => {
|
|
||||||
RespData::len() + head_len.0 + tag_len.0
|
|
||||||
}
|
|
||||||
RespState::CipherText(len) => *len,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Total length of the response handshake
|
|
||||||
pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize {
|
|
||||||
KeyID::len() + head_len.0 + self.data.len() + tag_len.0
|
|
||||||
}
|
|
||||||
/// Serialize into raw bytes
|
|
||||||
/// NOTE: assumes that there is exactly as much buffer as needed
|
|
||||||
pub fn serialize(
|
|
||||||
&self,
|
|
||||||
head_len: NonceLen,
|
|
||||||
_tag_len: TagLen,
|
|
||||||
out: &mut [u8],
|
|
||||||
) {
|
|
||||||
out[0..KeyID::len()]
|
|
||||||
.copy_from_slice(&self.client_key_id.0.to_le_bytes());
|
|
||||||
let start_data = KeyID::len() + head_len.0;
|
|
||||||
let end_data = start_data + self.data.len();
|
|
||||||
self.data.serialize(&mut out[start_data..end_data]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypted response data
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct RespData {
|
|
||||||
/// Client nonce, copied from the request
|
|
||||||
pub client_nonce: Nonce,
|
|
||||||
/// Server Connection ID
|
|
||||||
pub id: ID,
|
|
||||||
/// Service Connection ID
|
|
||||||
pub service_connection_id: ID,
|
|
||||||
/// Service encryption key
|
|
||||||
pub service_key: Secret,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RespData {
|
|
||||||
/// Return the expected length for buffer allocation
|
|
||||||
pub fn len() -> usize {
|
|
||||||
Nonce::len() + ID::len() + ID::len() + Secret::len()
|
|
||||||
}
|
|
||||||
/// Serialize the data into a buffer
|
|
||||||
/// NOTE: assumes that there is exactly asa much buffer as needed
|
|
||||||
pub fn serialize(&self, out: &mut [u8]) {
|
|
||||||
let mut start = 0;
|
|
||||||
let mut end = Nonce::len();
|
|
||||||
out[start..end].copy_from_slice(&self.client_nonce.0);
|
|
||||||
start = end;
|
|
||||||
end = end + ID::len();
|
|
||||||
self.id.serialize(&mut out[start..end]);
|
|
||||||
start = end;
|
|
||||||
end = end + ID::len();
|
|
||||||
self.service_connection_id.serialize(&mut out[start..end]);
|
|
||||||
start = end;
|
|
||||||
end = end + Secret::len();
|
|
||||||
out[start..end].copy_from_slice(self.service_key.as_ref());
|
|
||||||
}
|
|
||||||
/// Parse the cleartext raw data
|
|
||||||
pub fn deserialize(raw: &[u8]) -> Result<Self, Error> {
|
|
||||||
let raw_sized: &[u8; 16] = raw[..Nonce::len()].try_into().unwrap();
|
|
||||||
let client_nonce: Nonce = raw_sized.into();
|
|
||||||
let end = Nonce::len() + ID::len();
|
|
||||||
let id: ID =
|
|
||||||
u64::from_le_bytes(raw[Nonce::len()..end].try_into().unwrap())
|
|
||||||
.into();
|
|
||||||
if id.is_handshake() {
|
|
||||||
return Err(Error::Parsing);
|
|
||||||
}
|
|
||||||
let parsed = end;
|
|
||||||
let end = parsed + ID::len();
|
|
||||||
let service_connection_id: ID =
|
|
||||||
u64::from_le_bytes(raw[parsed..end].try_into().unwrap()).into();
|
|
||||||
if service_connection_id.is_handshake() {
|
|
||||||
return Err(Error::Parsing);
|
|
||||||
}
|
|
||||||
let parsed = end;
|
|
||||||
let end = parsed + Secret::len();
|
|
||||||
let raw_secret: &[u8; 32] = raw[parsed..end].try_into().unwrap();
|
|
||||||
let service_key = raw_secret.into();
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
client_nonce,
|
|
||||||
id,
|
|
||||||
service_connection_id,
|
|
||||||
service_key,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
//! Directory synchronized handshake, Response parsing
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
connection::{
|
||||||
|
handshake::{
|
||||||
|
self,
|
||||||
|
dirsync::{DirSync, Nonce},
|
||||||
|
Error,
|
||||||
|
},
|
||||||
|
ProtocolVersion, ID,
|
||||||
|
},
|
||||||
|
enc::{
|
||||||
|
asym::KeyID,
|
||||||
|
sym::{NonceLen, TagLen},
|
||||||
|
Secret,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Server response in a directory synchronized handshake
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Resp {
|
||||||
|
/// Tells the client with which key the exchange was done
|
||||||
|
pub client_key_id: KeyID,
|
||||||
|
/// actual response data, might be encrypted
|
||||||
|
pub data: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl handshake::Parsing for Resp {
|
||||||
|
fn deserialize(raw: &[u8]) -> Result<handshake::Data, Error> {
|
||||||
|
const MIN_PKT_LEN: usize = 68;
|
||||||
|
if raw.len() < MIN_PKT_LEN {
|
||||||
|
return Err(Error::NotEnoughData);
|
||||||
|
}
|
||||||
|
let client_key_id: KeyID =
|
||||||
|
KeyID(u16::from_le_bytes(raw[0..KeyID::len()].try_into().unwrap()));
|
||||||
|
Ok(handshake::Data::DirSync(DirSync::Resp(Self {
|
||||||
|
client_key_id,
|
||||||
|
data: State::CipherText(raw[KeyID::len()..].len()),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resp {
|
||||||
|
/// return the offset of the encrypted data
|
||||||
|
/// NOTE: starts from the beginning of the fenrir packet
|
||||||
|
pub fn encrypted_offset(&self) -> usize {
|
||||||
|
ProtocolVersion::len() + handshake::ID::len() + KeyID::len()
|
||||||
|
}
|
||||||
|
/// return the total length of the cleartext data
|
||||||
|
pub fn encrypted_length(
|
||||||
|
&self,
|
||||||
|
head_len: NonceLen,
|
||||||
|
tag_len: TagLen,
|
||||||
|
) -> usize {
|
||||||
|
match &self.data {
|
||||||
|
State::ClearText(_data) => Data::len() + head_len.0 + tag_len.0,
|
||||||
|
State::CipherText(len) => *len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Total length of the response handshake
|
||||||
|
pub fn len(&self, head_len: NonceLen, tag_len: TagLen) -> usize {
|
||||||
|
KeyID::len() + head_len.0 + self.data.len() + tag_len.0
|
||||||
|
}
|
||||||
|
/// Serialize into raw bytes
|
||||||
|
/// NOTE: assumes that there is exactly as much buffer as needed
|
||||||
|
pub fn serialize(
|
||||||
|
&self,
|
||||||
|
head_len: NonceLen,
|
||||||
|
_tag_len: TagLen,
|
||||||
|
out: &mut [u8],
|
||||||
|
) {
|
||||||
|
out[0..KeyID::len()]
|
||||||
|
.copy_from_slice(&self.client_key_id.0.to_le_bytes());
|
||||||
|
let start_data = KeyID::len() + head_len.0;
|
||||||
|
let end_data = start_data + self.data.len();
|
||||||
|
self.data.serialize(&mut out[start_data..end_data]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Quick way to avoid mixing cipher and clear text
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum State {
|
||||||
|
/// Server data, still in ciphertext
|
||||||
|
CipherText(usize),
|
||||||
|
/// Parsed, cleartext server data
|
||||||
|
ClearText(Data),
|
||||||
|
}
|
||||||
|
impl State {
|
||||||
|
/// The length of the data
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
State::CipherText(len) => *len,
|
||||||
|
State::ClearText(_) => Data::len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// parse the cleartext
|
||||||
|
pub fn deserialize_as_cleartext(
|
||||||
|
&mut self,
|
||||||
|
raw: &[u8],
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let clear = match self {
|
||||||
|
State::CipherText(len) => {
|
||||||
|
assert!(
|
||||||
|
*len > raw.len(),
|
||||||
|
"DirSync::State::CipherText length mismatch"
|
||||||
|
);
|
||||||
|
match Data::deserialize(raw) {
|
||||||
|
Ok(clear) => clear,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(Error::Parsing),
|
||||||
|
};
|
||||||
|
*self = State::ClearText(clear);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Serialize the still cleartext data
|
||||||
|
pub fn serialize(&self, out: &mut [u8]) {
|
||||||
|
if let State::ClearText(clear) = &self {
|
||||||
|
clear.serialize(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypted response data
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Data {
|
||||||
|
/// Client nonce, copied from the request
|
||||||
|
pub client_nonce: Nonce,
|
||||||
|
/// Server Connection ID
|
||||||
|
pub id: ID,
|
||||||
|
/// Service Connection ID
|
||||||
|
pub service_connection_id: ID,
|
||||||
|
/// Service encryption key
|
||||||
|
pub service_key: Secret,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Data {
|
||||||
|
/// Return the expected length for buffer allocation
|
||||||
|
pub fn len() -> usize {
|
||||||
|
Nonce::len() + ID::len() + ID::len() + Secret::len()
|
||||||
|
}
|
||||||
|
/// Serialize the data into a buffer
|
||||||
|
/// NOTE: assumes that there is exactly asa much buffer as needed
|
||||||
|
pub fn serialize(&self, out: &mut [u8]) {
|
||||||
|
let mut start = 0;
|
||||||
|
let mut end = Nonce::len();
|
||||||
|
out[start..end].copy_from_slice(&self.client_nonce.0);
|
||||||
|
start = end;
|
||||||
|
end = end + ID::len();
|
||||||
|
self.id.serialize(&mut out[start..end]);
|
||||||
|
start = end;
|
||||||
|
end = end + ID::len();
|
||||||
|
self.service_connection_id.serialize(&mut out[start..end]);
|
||||||
|
start = end;
|
||||||
|
end = end + Secret::len();
|
||||||
|
out[start..end].copy_from_slice(self.service_key.as_ref());
|
||||||
|
}
|
||||||
|
/// Parse the cleartext raw data
|
||||||
|
pub fn deserialize(raw: &[u8]) -> Result<Self, Error> {
|
||||||
|
let raw_sized: &[u8; 16] = raw[..Nonce::len()].try_into().unwrap();
|
||||||
|
let client_nonce: Nonce = raw_sized.into();
|
||||||
|
let end = Nonce::len() + ID::len();
|
||||||
|
let id: ID =
|
||||||
|
u64::from_le_bytes(raw[Nonce::len()..end].try_into().unwrap())
|
||||||
|
.into();
|
||||||
|
if id.is_handshake() {
|
||||||
|
return Err(Error::Parsing);
|
||||||
|
}
|
||||||
|
let parsed = end;
|
||||||
|
let end = parsed + ID::len();
|
||||||
|
let service_connection_id: ID =
|
||||||
|
u64::from_le_bytes(raw[parsed..end].try_into().unwrap()).into();
|
||||||
|
if service_connection_id.is_handshake() {
|
||||||
|
return Err(Error::Parsing);
|
||||||
|
}
|
||||||
|
let parsed = end;
|
||||||
|
let end = parsed + Secret::len();
|
||||||
|
let raw_secret: &[u8; 32] = raw[parsed..end].try_into().unwrap();
|
||||||
|
let service_key = raw_secret.into();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
client_nonce,
|
||||||
|
id,
|
||||||
|
service_connection_id,
|
||||||
|
service_key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -166,9 +166,11 @@ impl Handshake {
|
||||||
None => return Err(Error::Parsing),
|
None => return Err(Error::Parsing),
|
||||||
};
|
};
|
||||||
let data = match handshake_kind {
|
let data = match handshake_kind {
|
||||||
HandshakeKind::DirSyncReq => dirsync::Req::deserialize(&raw[2..])?,
|
HandshakeKind::DirSyncReq => {
|
||||||
|
dirsync::req::Req::deserialize(&raw[2..])?
|
||||||
|
}
|
||||||
HandshakeKind::DirSyncResp => {
|
HandshakeKind::DirSyncResp => {
|
||||||
dirsync::Resp::deserialize(&raw[2..])?
|
dirsync::resp::Resp::deserialize(&raw[2..])?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
|
@ -22,11 +22,11 @@ fn test_handshake_dirsync_req() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let data = dirsync::ReqState::ClearText(dirsync::ReqData {
|
let data = dirsync::req::State::ClearText(dirsync::req::Data {
|
||||||
nonce: dirsync::Nonce::new(&rand),
|
nonce: dirsync::Nonce::new(&rand),
|
||||||
client_key_id: KeyID(2424),
|
client_key_id: KeyID(2424),
|
||||||
id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()),
|
id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()),
|
||||||
auth: dirsync::AuthInfo {
|
auth: dirsync::req::AuthInfo {
|
||||||
user: auth::UserID::new(&rand),
|
user: auth::UserID::new(&rand),
|
||||||
token: auth::Token::new_anonymous(&rand),
|
token: auth::Token::new_anonymous(&rand),
|
||||||
service_id: auth::SERVICEID_AUTH,
|
service_id: auth::SERVICEID_AUTH,
|
||||||
|
@ -35,7 +35,7 @@ fn test_handshake_dirsync_req() {
|
||||||
});
|
});
|
||||||
|
|
||||||
let h_req = Handshake::new(handshake::Data::DirSync(
|
let h_req = Handshake::new(handshake::Data::DirSync(
|
||||||
dirsync::DirSync::Req(dirsync::Req {
|
dirsync::DirSync::Req(dirsync::req::Req {
|
||||||
key_id: KeyID(4224),
|
key_id: KeyID(4224),
|
||||||
exchange: enc::asym::KeyExchangeKind::X25519DiffieHellman,
|
exchange: enc::asym::KeyExchangeKind::X25519DiffieHellman,
|
||||||
hkdf: enc::hkdf::Kind::Sha3,
|
hkdf: enc::hkdf::Kind::Sha3,
|
||||||
|
@ -81,7 +81,7 @@ fn test_handshake_dirsync_reqsp() {
|
||||||
|
|
||||||
let service_key = enc::Secret::new_rand(&rand);
|
let service_key = enc::Secret::new_rand(&rand);
|
||||||
|
|
||||||
let data = dirsync::RespState::ClearText(dirsync::RespData {
|
let data = dirsync::resp::State::ClearText(dirsync::resp::Data {
|
||||||
client_nonce: dirsync::Nonce::new(&rand),
|
client_nonce: dirsync::Nonce::new(&rand),
|
||||||
id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()),
|
id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()),
|
||||||
service_connection_id: ID::ID(
|
service_connection_id: ID::ID(
|
||||||
|
@ -91,7 +91,7 @@ fn test_handshake_dirsync_reqsp() {
|
||||||
});
|
});
|
||||||
|
|
||||||
let h_resp = Handshake::new(handshake::Data::DirSync(
|
let h_resp = Handshake::new(handshake::Data::DirSync(
|
||||||
dirsync::DirSync::Resp(dirsync::Resp {
|
dirsync::DirSync::Resp(dirsync::resp::Resp {
|
||||||
client_key_id: KeyID(4444),
|
client_key_id: KeyID(4444),
|
||||||
data,
|
data,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -53,7 +53,7 @@ fn test_encrypt_decrypt() {
|
||||||
|
|
||||||
let service_key = enc::Secret::new_rand(&rand);
|
let service_key = enc::Secret::new_rand(&rand);
|
||||||
|
|
||||||
let data = dirsync::RespState::ClearText(dirsync::RespData {
|
let data = dirsync::resp::State::ClearText(dirsync::resp::Data {
|
||||||
client_nonce: dirsync::Nonce::new(&rand),
|
client_nonce: dirsync::Nonce::new(&rand),
|
||||||
id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()),
|
id: ID::ID(::core::num::NonZeroU64::new(424242).unwrap()),
|
||||||
service_connection_id: ID::ID(
|
service_connection_id: ID::ID(
|
||||||
|
@ -62,7 +62,7 @@ fn test_encrypt_decrypt() {
|
||||||
service_key,
|
service_key,
|
||||||
});
|
});
|
||||||
|
|
||||||
let resp = dirsync::Resp {
|
let resp = dirsync::resp::Resp {
|
||||||
client_key_id: KeyID(4444),
|
client_key_id: KeyID(4444),
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
|
|
|
@ -326,25 +326,25 @@ impl Worker {
|
||||||
};
|
};
|
||||||
|
|
||||||
// build request
|
// build request
|
||||||
let auth_info = dirsync::AuthInfo {
|
let auth_info = dirsync::req::AuthInfo {
|
||||||
user: UserID::new_anonymous(),
|
user: UserID::new_anonymous(),
|
||||||
token: Token::new_anonymous(&self.rand),
|
token: Token::new_anonymous(&self.rand),
|
||||||
service_id: conn_info.service_id,
|
service_id: conn_info.service_id,
|
||||||
domain: conn_info.domain,
|
domain: conn_info.domain,
|
||||||
};
|
};
|
||||||
let req_data = dirsync::ReqData {
|
let req_data = dirsync::req::Data {
|
||||||
nonce: dirsync::Nonce::new(&self.rand),
|
nonce: dirsync::Nonce::new(&self.rand),
|
||||||
client_key_id,
|
client_key_id,
|
||||||
id: auth_recv_id.0, //FIXME: is zero
|
id: auth_recv_id.0, //FIXME: is zero
|
||||||
auth: auth_info,
|
auth: auth_info,
|
||||||
};
|
};
|
||||||
let req = dirsync::Req {
|
let req = dirsync::req::Req {
|
||||||
key_id: key.0,
|
key_id: key.0,
|
||||||
exchange,
|
exchange,
|
||||||
hkdf: hkdf_selected,
|
hkdf: hkdf_selected,
|
||||||
cipher: cipher_selected,
|
cipher: cipher_selected,
|
||||||
exchange_key: pub_key,
|
exchange_key: pub_key,
|
||||||
data: dirsync::ReqState::ClearText(req_data),
|
data: dirsync::req::State::ClearText(req_data),
|
||||||
};
|
};
|
||||||
let encrypt_start =
|
let encrypt_start =
|
||||||
connection::ID::len() + req.encrypted_offset();
|
connection::ID::len() + req.encrypted_offset();
|
||||||
|
@ -459,9 +459,8 @@ impl Worker {
|
||||||
::tracing::error!("AuthInfo on non DS::Req");
|
::tracing::error!("AuthInfo on non DS::Req");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
use dirsync::ReqState;
|
|
||||||
let req_data = match req.data {
|
let req_data = match req.data {
|
||||||
ReqState::ClearText(req_data) => req_data,
|
dirsync::req::State::ClearText(req_data) => req_data,
|
||||||
_ => {
|
_ => {
|
||||||
::tracing::error!("AuthNeeded: expected ClearText");
|
::tracing::error!("AuthNeeded: expected ClearText");
|
||||||
assert!(false, "AuthNeeded: unreachable");
|
assert!(false, "AuthNeeded: unreachable");
|
||||||
|
@ -527,7 +526,7 @@ impl Worker {
|
||||||
let auth_id_recv = self.connections.reserve_first();
|
let auth_id_recv = self.connections.reserve_first();
|
||||||
auth_conn.id_recv = auth_id_recv;
|
auth_conn.id_recv = auth_id_recv;
|
||||||
|
|
||||||
let resp_data = dirsync::RespData {
|
let resp_data = dirsync::resp::Data {
|
||||||
client_nonce: req_data.nonce,
|
client_nonce: req_data.nonce,
|
||||||
id: auth_conn.id_recv.0,
|
id: auth_conn.id_recv.0,
|
||||||
service_connection_id: srv_conn_id,
|
service_connection_id: srv_conn_id,
|
||||||
|
@ -537,10 +536,9 @@ impl Worker {
|
||||||
// no aad for now
|
// no aad for now
|
||||||
let aad = AAD(&mut []);
|
let aad = AAD(&mut []);
|
||||||
|
|
||||||
use dirsync::RespState;
|
let resp = dirsync::resp::Resp {
|
||||||
let resp = dirsync::Resp {
|
|
||||||
client_key_id: req_data.client_key_id,
|
client_key_id: req_data.client_key_id,
|
||||||
data: RespState::ClearText(resp_data),
|
data: dirsync::resp::State::ClearText(resp_data),
|
||||||
};
|
};
|
||||||
let encrypt_from =
|
let encrypt_from =
|
||||||
connection::ID::len() + resp.encrypted_offset();
|
connection::ID::len() + resp.encrypted_offset();
|
||||||
|
@ -579,7 +577,8 @@ impl Worker {
|
||||||
}
|
}
|
||||||
// track connection
|
// track connection
|
||||||
let resp_data;
|
let resp_data;
|
||||||
if let dirsync::RespState::ClearText(r_data) = ds_resp.data
|
if let dirsync::resp::State::ClearText(r_data) =
|
||||||
|
ds_resp.data
|
||||||
{
|
{
|
||||||
resp_data = r_data;
|
resp_data = r_data;
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue