Initial Dosh implementation
This commit is contained in:
+330
@@ -0,0 +1,330 @@
|
||||
use crate::auth::BootstrapResponse;
|
||||
use crate::crypto;
|
||||
use anyhow::{Context, Result, bail};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const MAGIC: &[u8; 4] = b"DOSH";
|
||||
pub const VERSION: u8 = 1;
|
||||
pub const HEADER_LEN: usize = 42;
|
||||
pub const CLIENT_TO_SERVER: u32 = 1;
|
||||
pub const SERVER_TO_CLIENT: u32 = 2;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum PacketKind {
|
||||
BootstrapAttachRequest = 1,
|
||||
TicketAttachRequest = 2,
|
||||
AttachOk = 3,
|
||||
AttachReject = 4,
|
||||
ResumeRequest = 5,
|
||||
ResumeOk = 6,
|
||||
Input = 7,
|
||||
Resize = 8,
|
||||
Frame = 9,
|
||||
Ack = 10,
|
||||
Ping = 11,
|
||||
Pong = 12,
|
||||
Detach = 13,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for PacketKind {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self> {
|
||||
Ok(match value {
|
||||
1 => Self::BootstrapAttachRequest,
|
||||
2 => Self::TicketAttachRequest,
|
||||
3 => Self::AttachOk,
|
||||
4 => Self::AttachReject,
|
||||
5 => Self::ResumeRequest,
|
||||
6 => Self::ResumeOk,
|
||||
7 => Self::Input,
|
||||
8 => Self::Resize,
|
||||
9 => Self::Frame,
|
||||
10 => Self::Ack,
|
||||
11 => Self::Ping,
|
||||
12 => Self::Pong,
|
||||
13 => Self::Detach,
|
||||
_ => bail!("unknown packet kind {value}"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Header {
|
||||
pub kind: PacketKind,
|
||||
pub flags: u16,
|
||||
pub conn_id: [u8; 16],
|
||||
pub seq: u64,
|
||||
pub ack: u64,
|
||||
pub body_len: u16,
|
||||
}
|
||||
|
||||
impl Header {
|
||||
pub fn aad(&self) -> [u8; HEADER_LEN] {
|
||||
let mut out = [0u8; HEADER_LEN];
|
||||
out[..4].copy_from_slice(MAGIC);
|
||||
out[4] = VERSION;
|
||||
out[5] = self.kind as u8;
|
||||
out[6..8].copy_from_slice(&self.flags.to_be_bytes());
|
||||
out[8..24].copy_from_slice(&self.conn_id);
|
||||
out[24..32].copy_from_slice(&self.seq.to_be_bytes());
|
||||
out[32..40].copy_from_slice(&self.ack.to_be_bytes());
|
||||
out[40..42].copy_from_slice(&self.body_len.to_be_bytes());
|
||||
out
|
||||
}
|
||||
|
||||
pub fn parse(input: &[u8]) -> Result<Self> {
|
||||
if input.len() < HEADER_LEN {
|
||||
bail!("packet too short");
|
||||
}
|
||||
if &input[..4] != MAGIC {
|
||||
bail!("bad magic");
|
||||
}
|
||||
if input[4] != VERSION {
|
||||
bail!("bad protocol version {}", input[4]);
|
||||
}
|
||||
let kind = PacketKind::try_from(input[5])?;
|
||||
let flags = u16::from_be_bytes(input[6..8].try_into().unwrap());
|
||||
let mut conn_id = [0u8; 16];
|
||||
conn_id.copy_from_slice(&input[8..24]);
|
||||
let seq = u64::from_be_bytes(input[24..32].try_into().unwrap());
|
||||
let ack = u64::from_be_bytes(input[32..40].try_into().unwrap());
|
||||
let body_len = u16::from_be_bytes(input[40..42].try_into().unwrap());
|
||||
Ok(Self {
|
||||
kind,
|
||||
flags,
|
||||
conn_id,
|
||||
seq,
|
||||
ack,
|
||||
body_len,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Packet {
|
||||
pub header: Header,
|
||||
pub body: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn encode_plain(
|
||||
kind: PacketKind,
|
||||
conn_id: [u8; 16],
|
||||
seq: u64,
|
||||
ack: u64,
|
||||
body: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
if body.len() > u16::MAX as usize {
|
||||
bail!("packet body too large");
|
||||
}
|
||||
let header = Header {
|
||||
kind,
|
||||
flags: 0,
|
||||
conn_id,
|
||||
seq,
|
||||
ack,
|
||||
body_len: body.len() as u16,
|
||||
};
|
||||
let mut out = Vec::with_capacity(HEADER_LEN + body.len());
|
||||
out.extend_from_slice(&header.aad());
|
||||
out.extend_from_slice(body);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn encode_encrypted(
|
||||
kind: PacketKind,
|
||||
conn_id: [u8; 16],
|
||||
seq: u64,
|
||||
ack: u64,
|
||||
key: &[u8; 32],
|
||||
direction: u32,
|
||||
plaintext: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
let nonce = crypto::nonce_from(direction, seq);
|
||||
let header = Header {
|
||||
kind,
|
||||
flags: 1,
|
||||
conn_id,
|
||||
seq,
|
||||
ack,
|
||||
body_len: 0,
|
||||
};
|
||||
let aad_without_len = header.aad();
|
||||
let ciphertext = crypto::seal(key, &nonce, &aad_without_len[..40], plaintext)?;
|
||||
if ciphertext.len() > u16::MAX as usize {
|
||||
bail!("packet body too large");
|
||||
}
|
||||
let header = Header {
|
||||
body_len: ciphertext.len() as u16,
|
||||
..header
|
||||
};
|
||||
let mut out = Vec::with_capacity(HEADER_LEN + ciphertext.len());
|
||||
out.extend_from_slice(&header.aad());
|
||||
out.extend_from_slice(&ciphertext);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn decode(input: &[u8]) -> Result<Packet> {
|
||||
let header = Header::parse(input)?;
|
||||
let end = HEADER_LEN + header.body_len as usize;
|
||||
if input.len() < end {
|
||||
bail!("truncated packet body");
|
||||
}
|
||||
Ok(Packet {
|
||||
header,
|
||||
body: input[HEADER_LEN..end].to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decrypt_body(packet: &Packet, key: &[u8; 32], direction: u32) -> Result<Vec<u8>> {
|
||||
if packet.header.flags & 1 == 0 {
|
||||
return Ok(packet.body.clone());
|
||||
}
|
||||
let nonce = crypto::nonce_from(direction, packet.header.seq);
|
||||
let aad = packet.header.aad();
|
||||
crypto::open(key, &nonce, &aad[..40], &packet.body)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BootstrapAttachRequest {
|
||||
pub bootstrap: BootstrapResponse,
|
||||
pub cols: u16,
|
||||
pub rows: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TicketAttachEnvelope {
|
||||
pub ticket: Vec<u8>,
|
||||
pub client_nonce: [u8; 12],
|
||||
pub ciphertext: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TicketAttachBody {
|
||||
pub session: String,
|
||||
pub mode: String,
|
||||
pub cols: u16,
|
||||
pub rows: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TicketAttachOkEnvelope {
|
||||
pub server_nonce: [u8; 12],
|
||||
pub ciphertext: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AttachOk {
|
||||
pub client_id: [u8; 16],
|
||||
pub session: String,
|
||||
pub mode: String,
|
||||
pub session_key: [u8; 32],
|
||||
pub session_key_id: [u8; 16],
|
||||
pub initial_seq: u64,
|
||||
pub snapshot: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AttachReject {
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ResumeRequest {
|
||||
pub session: String,
|
||||
pub last_rendered_seq: u64,
|
||||
pub cols: u16,
|
||||
pub rows: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Input {
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Resize {
|
||||
pub cols: u16,
|
||||
pub rows: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Frame {
|
||||
pub session: String,
|
||||
pub output_seq: u64,
|
||||
pub bytes: Vec<u8>,
|
||||
pub snapshot: bool,
|
||||
}
|
||||
|
||||
pub fn to_body<T: Serialize>(value: &T) -> Result<Vec<u8>> {
|
||||
bincode::serialize(value).context("serialize protocol body")
|
||||
}
|
||||
|
||||
pub fn from_body<T: for<'de> Deserialize<'de>>(body: &[u8]) -> Result<T> {
|
||||
bincode::deserialize(body).context("deserialize protocol body")
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReplayWindow {
|
||||
highest: u64,
|
||||
seen: u128,
|
||||
width: u32,
|
||||
}
|
||||
|
||||
impl Default for ReplayWindow {
|
||||
fn default() -> Self {
|
||||
Self::new(128)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplayWindow {
|
||||
pub fn new(width: u32) -> Self {
|
||||
assert!((1..=128).contains(&width));
|
||||
Self {
|
||||
highest: 0,
|
||||
seen: 0,
|
||||
width,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accept(&mut self, seq: u64) -> bool {
|
||||
if seq == 0 {
|
||||
return false;
|
||||
}
|
||||
if self.highest == 0 {
|
||||
self.highest = seq;
|
||||
self.seen = 1;
|
||||
return true;
|
||||
}
|
||||
if seq > self.highest {
|
||||
let shift = (seq - self.highest).min(128) as u32;
|
||||
self.seen = if shift >= self.width {
|
||||
1
|
||||
} else {
|
||||
((self.seen << shift) | 1) & self.mask()
|
||||
};
|
||||
self.highest = seq;
|
||||
return true;
|
||||
}
|
||||
let offset = self.highest - seq;
|
||||
if offset >= self.width as u64 {
|
||||
return false;
|
||||
}
|
||||
let bit = 1u128 << offset;
|
||||
if self.seen & bit != 0 {
|
||||
false
|
||||
} else {
|
||||
self.seen |= bit;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn mask(&self) -> u128 {
|
||||
if self.width == 128 {
|
||||
u128::MAX
|
||||
} else {
|
||||
(1u128 << self.width) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user