This commit is contained in:
nora 2023-09-08 23:32:17 +02:00
parent 93053aa6eb
commit c71fc68d8e
2 changed files with 195 additions and 41 deletions

View file

@ -2,12 +2,10 @@ pub mod proto;
use std::{ use std::{
fmt::Debug, fmt::Debug,
io::{self}, io::{self, BufWriter, Read, Write},
net::{TcpStream, ToSocketAddrs}, net::{TcpStream, ToSocketAddrs},
}; };
use crate::proto::CipherSuite;
type Result<T, E = Error> = std::result::Result<T, E>; type Result<T, E = Error> = std::result::Result<T, E>;
pub struct ClientConnection {} pub struct ClientConnection {}
@ -24,19 +22,26 @@ struct ClientSetupConnection {}
impl ClientSetupConnection { impl ClientSetupConnection {
fn establish(host: impl ToSocketAddrs) -> Result<Self> { fn establish(host: impl ToSocketAddrs) -> Result<Self> {
let mut stream = TcpStream::connect(host)?; let mut stream = BufWriter::new(LoggingWriter(TcpStream::connect(host)?));
let handshake = proto::Handshake::ClientHello { let handshake = proto::Handshake::ClientHello {
legacy_version: proto::LEGACY_VERSION, legacy_version: proto::LEGACY_VERSION,
random: rand::random(), random: rand::random(),
legacy_session_id: 0, legacy_session_id: 0,
cipher_suites: vec![CipherSuite::TlsAes128GcmSha256].into(), cipher_suites: vec![proto::CipherSuite::TlsAes128GcmSha256].into(),
legacy_compressions_methods: 0, legacy_compressions_methods: 0,
extensions: vec![].into(), extensions: vec![proto::ExtensionCH::SupportedVersions {
versions: vec![proto::TLSV3].into(),
}]
.into(),
}; };
proto::write_handshake(&mut stream, handshake)?; let plaintext = proto::TLSPlaintext::Handshake {
handshake,
};
plaintext.write(&mut stream)?;
stream.flush()?;
let res = proto::read_handshake(&mut stream)?; // let res: proto::TLSPlaintext = proto::Value::read(&mut stream.get_mut())?;
dbg!(res); // dbg!(res);
todo!() todo!()
} }
@ -66,3 +71,30 @@ impl From<ErrorKind> for Error {
Self { kind: value } Self { kind: value }
} }
} }
#[derive(Debug)]
struct LoggingWriter<W>(W);
impl<W: io::Write> io::Write for LoggingWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let len = self.0.write(buf);
if let Ok(len) = len {
eprintln!("wrote bytes: {:x?}", &buf[..len]);
}
len
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl<R: Read> io::Read for LoggingWriter<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let len = self.0.read(buf);
if let Ok(len) = len {
eprintln!("read bytes: {:x?}", &buf[..len]);
}
len
}
}

View file

@ -8,26 +8,65 @@ use byteorder::{BigEndian as B, ReadBytesExt, WriteBytesExt};
use crate::ErrorKind; use crate::ErrorKind;
#[derive(Debug, Clone)]
pub enum TLSPlaintext {
Invalid {
legacy_version: ProtocolVersion,
fragment: List<u8, u16>,
},
ChangeCipherSpec,
Alert,
Handshake {
handshake: Handshake,
},
ApplicationData,
}
impl TLSPlaintext {
pub fn write(&self, w: &mut impl Write) -> io::Result<()> {
match self {
TLSPlaintext::Invalid {
legacy_version,
fragment,
} => todo!(),
TLSPlaintext::ChangeCipherSpec => todo!(),
TLSPlaintext::Alert => todo!(),
TLSPlaintext::Handshake { handshake } => {
22u8.write(w)?; // handshake
LEGACY_VERSION.write(w)?;
let len: u16 = handshake.byte_size().try_into().unwrap();
len.write(w)?;
handshake.write(w)?;
Ok(())
}
TLSPlaintext::ApplicationData => todo!(),
}
}
}
pub type ProtocolVersion = u16;
pub type Random = [u8; 32];
// https://datatracker.ietf.org/doc/html/rfc8446#section-4 // https://datatracker.ietf.org/doc/html/rfc8446#section-4
proto_enum! { proto_enum! {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Handshake: u8 { pub enum Handshake: u8 {
// https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2 // https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2
ClientHello { ClientHello {
legacy_version: u16, legacy_version: ProtocolVersion,
random: [u8; 32], random: Random,
legacy_session_id: u8, legacy_session_id: u8,
cipher_suites: List<CipherSuite, u16>, cipher_suites: List<CipherSuite, u16>,
legacy_compressions_methods: u8, legacy_compressions_methods: u8,
extensions: List<Extension, u16>, extensions: List<ExtensionCH, u16>,
} = 1, } = 1,
ServerHello { ServerHello {
legacy_version: u16, legacy_version: ProtocolVersion,
random: [u8; 32], random: Random,
legacy_session_id_echo: u8, legacy_session_id_echo: u8,
cipher_suite: CipherSuite, cipher_suite: CipherSuite,
legacy_compression_method: u8, legacy_compression_method: u8,
extensions: List<Extension, u16>, extensions: List<ExtensionSH, u16>,
} = 2, } = 2,
NewSessionTicket {} = 4, NewSessionTicket {} = 4,
EndOfEarlyData {} = 5, EndOfEarlyData {} = 5,
@ -41,7 +80,8 @@ proto_enum! {
} }
} }
pub const LEGACY_VERSION: u16 = 0x0303; // TLS v1.2 pub const LEGACY_VERSION: ProtocolVersion = 0x0303; // TLS v1.2
pub const TLSV3: ProtocolVersion = 0x0304;
proto_enum! { proto_enum! {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -54,11 +94,9 @@ proto_enum! {
} }
} }
pub type Extension = (ExtensionType, List<u8, u16>);
proto_enum! { proto_enum! {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub enum ExtensionType: u16 { pub enum ExtensionCH: u16 {
ServerName = 0, ServerName = 0,
MaxFragmentLength = 1, MaxFragmentLength = 1,
StatusRequest = 5, StatusRequest = 5,
@ -73,19 +111,67 @@ proto_enum! {
Padding = 21, Padding = 21,
PreSharedKey = 41, PreSharedKey = 41,
EarlyData = 42, EarlyData = 42,
SupportedVersions = 43, SupportedVersions {
versions: List<ProtocolVersion, u8>,
} = 43,
Cookie = 44, Cookie = 44,
PskKeyExchangeModes = 45, PskKeyExchangeModes = 45,
CertificateAuthorities = 47, CertificateAuthorities = 47,
OidFilters = 48,
PostHandshakeAuth = 49, PostHandshakeAuth = 49,
SignatureAlgorithmsCert = 50, SignatureAlgorithmsCert = 50,
KeyShare = 51, KeyShare = 51,
} }
} }
proto_enum! {
#[derive(Debug, Clone, Copy)]
pub enum ExtensionSH: u16 {
PreSharedKey = 41,
SupportedVersions {
selected_version: ProtocolVersion,
} = 43,
KeyShare = 51,
}
}
macro_rules! proto_struct {
{$(#[$meta:meta])* pub struct $name:ident {
$(
$field_name:ident : $field_ty:ty,
)*
}} => {
$(#[$meta])*
pub struct $name {
$(
$field_name: $field_ty,
)*
}
impl Value for $name {
fn write<W: Write>(&self, mut w: &mut W) -> io::Result<()> {
$(
Value::write(&self.$field_name, &mut w)?;
)*
Ok(())
}
fn read<R: Read>(r: &mut R) -> crate::Result<Self> {
let ( $( $field_name ),* ) = ($( { discard!($field_name); Value::read(r)? } ),*);
Ok(Self {
$(
$field_name,
)*
})
}
}
};
}
use proto_struct;
macro_rules! proto_enum { macro_rules! proto_enum {
($(#[$meta:meta])* pub enum $name:ident: $discr_ty:ty { {$(#[$meta:meta])* pub enum $name:ident: $discr_ty:ty {
$( $(
$KindName:ident $({ $KindName:ident $({
$( $(
@ -93,7 +179,7 @@ macro_rules! proto_enum {
)* )*
})? = $discriminant:expr, })? = $discriminant:expr,
)* )*
}) => { }} => {
$(#[$meta])* $(#[$meta])*
pub enum $name { pub enum $name {
$( $(
@ -141,6 +227,7 @@ macro_rules! proto_enum {
match kind { match kind {
$( $(
discr_consts::$KindName => { discr_consts::$KindName => {
#[allow(unused_parens)]
$(let ( $( $field_name ),* ) = ($( { discard!($field_name); Value::read(r)? } ),*);)? $(let ( $( $field_name ),* ) = ($( { discard!($field_name); Value::read(r)? } ),*);)?
Ok(Self::$KindName $({ Ok(Self::$KindName $({
@ -154,6 +241,26 @@ macro_rules! proto_enum {
_ => Err(ErrorKind::InvalidHandshake(Box::new(kind)).into()), _ => Err(ErrorKind::InvalidHandshake(Box::new(kind)).into()),
} }
} }
fn byte_size(&self) -> usize {
mod discr_consts {
$(
#[allow(non_upper_case_globals)]
pub(super) const $KindName: $discr_ty = $discriminant;
)*
}
match self {
$(
Self::$KindName $( {
$( $field_name, )*
} )? => {
$( $( $field_name.byte_size() + )* )? discr_consts::$KindName.byte_size()
}
)*
}
}
} }
}; };
} }
@ -174,38 +281,40 @@ impl<T: Debug, Len> Debug for List<T, Len> {
} }
} }
impl<T: Value, Len: Value + Into<usize> + TryFrom<usize>> Value for List<T, Len> { impl<T: Value, Len: Value + Into<usize> + TryFrom<usize> + Default> Value for List<T, Len> {
fn read<R: Read>(r: &mut R) -> crate::Result<Self> { fn read<R: Read>(r: &mut R) -> crate::Result<Self> {
let len: usize = Len::read(r)?.into(); let mut remaining_byte_size = Len::read(r)?.into();
let mut v = Vec::with_capacity(len.max(1000)); let mut v = Vec::new();
for _ in 0..len {
v.push(T::read(r)?); while remaining_byte_size > 0 {
let value = T::read(r)?;
remaining_byte_size -= value.byte_size();
v.push(value);
} }
Ok(Self(v, PhantomData)) Ok(Self(v, PhantomData))
} }
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> { fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
let byte_size = self.0.iter().map(Value::byte_size).sum::<usize>();
Len::write( Len::write(
&self &byte_size
.0
.len()
.try_into() .try_into()
.unwrap_or_else(|_| panic!("list is too large for domain: {}", self.0.len())), .unwrap_or_else(|_| panic!("list is too large for domain: {}", self.0.len())),
w, w,
) )?;
for elem in &self.0 {
elem.write(w)?;
}
Ok(())
}
fn byte_size(&self) -> usize {
Len::byte_size(&Default::default()) + self.0.iter().map(Value::byte_size).sum::<usize>()
} }
}
pub fn write_handshake<W: Write>(w: &mut W, handshake: Handshake) -> io::Result<()> {
handshake.write(w)
}
pub fn read_handshake<R: Read>(r: &mut R) -> crate::Result<Handshake> {
Handshake::read(r)
} }
pub trait Value: Sized + std::fmt::Debug { pub trait Value: Sized + std::fmt::Debug {
fn write<W: Write>(&self, w: &mut W) -> io::Result<()>; fn write<W: Write>(&self, w: &mut W) -> io::Result<()>;
fn read<R: Read>(r: &mut R) -> crate::Result<Self>; fn read<R: Read>(r: &mut R) -> crate::Result<Self>;
fn byte_size(&self) -> usize;
} }
impl<V: Value, const N: usize> Value for [V; N] { impl<V: Value, const N: usize> Value for [V; N] {
@ -221,6 +330,9 @@ impl<V: Value, const N: usize> Value for [V; N] {
} }
Ok(values.try_into().unwrap()) Ok(values.try_into().unwrap())
} }
fn byte_size(&self) -> usize {
self.iter().map(Value::byte_size).sum()
}
} }
impl Value for u8 { impl Value for u8 {
@ -230,6 +342,9 @@ impl Value for u8 {
fn read<R: Read>(r: &mut R) -> crate::Result<Self> { fn read<R: Read>(r: &mut R) -> crate::Result<Self> {
r.read_u8().map_err(Into::into) r.read_u8().map_err(Into::into)
} }
fn byte_size(&self) -> usize {
1
}
} }
impl Value for u16 { impl Value for u16 {
@ -239,6 +354,9 @@ impl Value for u16 {
fn read<R: Read>(r: &mut R) -> crate::Result<Self> { fn read<R: Read>(r: &mut R) -> crate::Result<Self> {
r.read_u16::<B>().map_err(Into::into) r.read_u16::<B>().map_err(Into::into)
} }
fn byte_size(&self) -> usize {
2
}
} }
impl<T: Value, U: Value> Value for (T, U) { impl<T: Value, U: Value> Value for (T, U) {
@ -251,6 +369,10 @@ impl<T: Value, U: Value> Value for (T, U) {
fn read<R: Read>(r: &mut R) -> crate::Result<Self> { fn read<R: Read>(r: &mut R) -> crate::Result<Self> {
Ok((T::read(r)?, U::read(r)?)) Ok((T::read(r)?, U::read(r)?))
} }
fn byte_size(&self) -> usize {
self.0.byte_size() + self.1.byte_size()
}
} }
macro_rules! discard { macro_rules! discard {