This commit is contained in:
nora 2023-09-09 18:00:34 +02:00
parent 29057b8e92
commit 4b1d2805e6
3 changed files with 130 additions and 31 deletions

View file

@ -11,8 +11,8 @@ type Result<T, E = Error> = std::result::Result<T, E>;
pub struct ClientConnection {} pub struct ClientConnection {}
impl ClientConnection { impl ClientConnection {
pub fn establish(host: impl ToSocketAddrs) -> Result<Self> { pub fn establish(host: &str, port: u16) -> Result<Self> {
let _setup = ClientSetupConnection::establish(host)?; let _setup = ClientSetupConnection::establish(host, port)?;
todo!() todo!()
} }
@ -21,22 +21,28 @@ impl ClientConnection {
struct ClientSetupConnection {} struct ClientSetupConnection {}
impl ClientSetupConnection { impl ClientSetupConnection {
fn establish(host: impl ToSocketAddrs) -> Result<Self> { fn establish(host: &str, port: u16) -> Result<Self> {
let mut stream = BufWriter::new(LoggingWriter(TcpStream::connect(host)?)); let mut stream = BufWriter::new(LoggingWriter(TcpStream::connect((host, port))?));
let handshake = proto::Handshake::ClientHello { let handshake = proto::Handshake::ClientHello {
legacy_version: proto::LEGACY_VERSION, legacy_version: proto::LEGACY_TLSV12,
random: rand::random(), random: rand::random(),
legacy_session_id: 0, legacy_session_id: [(); 32].map(|()| rand::random()).to_vec().into(),
cipher_suites: vec![proto::CipherSuite::TlsAes128GcmSha256].into(), cipher_suites: vec![proto::CipherSuite::TlsAes128GcmSha256].into(),
legacy_compressions_methods: 0, legacy_compressions_methods: vec![0].into(),
extensions: vec![proto::ExtensionCH::SupportedVersions { extensions: vec![
versions: vec![proto::TLSV3].into(), proto::ExtensionCH::ServerName {
}] server_name: vec![proto::ServerName::HostName {
host_name: host.as_bytes().to_vec().into(),
}]
.into(),
},
proto::ExtensionCH::SupportedVersions {
versions: vec![proto::TLSV13].into(),
},
]
.into(), .into(),
}; };
let plaintext = proto::TLSPlaintext::Handshake { let plaintext = proto::TLSPlaintext::Handshake { handshake };
handshake,
};
plaintext.write(&mut stream)?; plaintext.write(&mut stream)?;
stream.flush()?; stream.flush()?;
@ -82,7 +88,7 @@ impl<W: io::Write> io::Write for LoggingWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let len = self.0.write(buf); let len = self.0.write(buf);
if let Ok(len) = len { if let Ok(len) = len {
eprintln!("wrote bytes: {:x?}", &buf[..len]); eprintln!(" bytes: {:02x?}", &buf[..len]);
} }
len len
} }
@ -96,7 +102,7 @@ impl<R: Read> io::Read for LoggingWriter<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let len = self.0.read(buf); let len = self.0.read(buf);
if let Ok(len) = len { if let Ok(len) = len {
eprintln!("read bytes: {:x?}", &buf[..len]); eprintln!("read bytes: {:02x?}", &buf[..len]);
} }
len len
} }

View file

@ -1,4 +1,4 @@
// An example program that makes a shitty HTTP/1.1 request. // An example program that makes a shitty HTTP/1.1 request.
fn main() { fn main() {
tls::ClientConnection::establish(("nilstrieb.dev", 443)).unwrap(); tls::ClientConnection::establish("nilstrieb.dev", 443).unwrap();
} }

View file

@ -2,6 +2,7 @@ use std::{
fmt::Debug, fmt::Debug,
io::{self, Read, Write}, io::{self, Read, Write},
marker::PhantomData, marker::PhantomData,
num::TryFromIntError,
}; };
use byteorder::{BigEndian as B, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian as B, ReadBytesExt, WriteBytesExt};
@ -40,8 +41,16 @@ impl TLSPlaintext {
TLSPlaintext::ChangeCipherSpec => todo!(), TLSPlaintext::ChangeCipherSpec => todo!(),
TLSPlaintext::Alert { alert } => todo!(), TLSPlaintext::Alert { alert } => todo!(),
TLSPlaintext::Handshake { handshake } => { TLSPlaintext::Handshake { handshake } => {
Self::HANDSHAKE.write(w)?; // handshake Self::HANDSHAKE.write(w)?;
LEGACY_VERSION.write(w)?; // MUST be set to 0x0303 for all records
// generated by a TLS 1.3 implementation other than an initial
// ClientHello (i.e., one not generated after a HelloRetryRequest),
// where it MAY also be 0x0301 for compatibility purposes.
if matches!(handshake, Handshake::ClientHello { .. }) {
LEGACY_TLSV10.write(w)?;
} else {
LEGACY_TLSV12.write(w)?;
}
let len: u16 = handshake.byte_size().try_into().unwrap(); let len: u16 = handshake.byte_size().try_into().unwrap();
len.write(w)?; len.write(w)?;
handshake.write(w)?; handshake.write(w)?;
@ -80,14 +89,14 @@ 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, (length: u24) {
// 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: ProtocolVersion, legacy_version: ProtocolVersion,
random: Random, random: Random,
legacy_session_id: u8, legacy_session_id: List<u8, u8>,
cipher_suites: List<CipherSuite, u16>, cipher_suites: List<CipherSuite, u16>,
legacy_compressions_methods: u8, legacy_compressions_methods: List<u8, u8>,
extensions: List<ExtensionCH, u16>, extensions: List<ExtensionCH, u16>,
} = 1, } = 1,
ServerHello { ServerHello {
@ -110,8 +119,9 @@ proto_enum! {
} }
} }
pub const LEGACY_VERSION: ProtocolVersion = 0x0303; // TLS v1.2 pub const LEGACY_TLSV10: ProtocolVersion = 0x0301;
pub const TLSV3: ProtocolVersion = 0x0304; pub const LEGACY_TLSV12: ProtocolVersion = 0x0303;
pub const TLSV13: ProtocolVersion = 0x0304;
proto_enum! { proto_enum! {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -126,8 +136,10 @@ proto_enum! {
proto_enum! { proto_enum! {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ExtensionCH: u16 { pub enum ExtensionCH: u16, (length: u16) {
ServerName = 0, ServerName {
server_name: ServerNameList,
} = 0,
MaxFragmentLength = 1, MaxFragmentLength = 1,
StatusRequest = 5, StatusRequest = 5,
SupportedGroups = 10, SupportedGroups = 10,
@ -164,6 +176,18 @@ proto_enum! {
} }
} }
proto_enum! {
#[derive(Debug, Clone)]
pub enum ServerName: u8 {
HostName {
host_name: HostName,
} = 0,
}
}
type HostName = List<u8, u16>;
type ServerNameList = List<ServerName, u16>;
proto_struct! { proto_struct! {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Alert { pub struct Alert {
@ -254,7 +278,7 @@ macro_rules! proto_struct {
use proto_struct; 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 $( ,(length: $len_ty:ty) )? {
$( $(
$KindName:ident $({ $KindName:ident $({
$( $(
@ -275,7 +299,9 @@ macro_rules! proto_enum {
} }
impl Value for $name { impl Value for $name {
fn write<W: Write>(&self, mut w: &mut W) -> io::Result<()> { fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.flush()?;
eprintln!("{}", stringify!($name));
mod discr_consts { mod discr_consts {
$( $(
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
@ -283,15 +309,35 @@ macro_rules! proto_enum {
)* )*
} }
let write_len = |_w: &mut W, _len: usize| -> io::Result<()> {
_w.flush()?;
eprintln!("length");
$(
<$len_ty>::try_from(_len).unwrap().write(_w)?;
)?
Ok(())
};
match self { match self {
$( $(
Self::$KindName $( { Self::$KindName $( {
$( $field_name, )* $( $field_name, )*
} )? => { } )? => {
Value::write(&discr_consts::$KindName, &mut w)?; let byte_size = $($( $field_name.byte_size() + )*)? 0;
Value::write(&discr_consts::$KindName, w)?;
write_len(w, byte_size)?;
let w = &mut MeasuringWriter(0, w);
$($( $($(
Value::write($field_name, &mut w)?; w.flush()?;
eprintln!("{}", stringify!($field_name));
Value::write($field_name, w)?;
)*)? )*)?
debug_assert_eq!(w.0, byte_size);
Ok(()) Ok(())
} }
)* )*
@ -307,6 +353,11 @@ macro_rules! proto_enum {
} }
let kind: $discr_ty = Value::read(r)?; let kind: $discr_ty = Value::read(r)?;
$(
let _len = <$len_ty>::read(r)?;
)?
match kind { match kind {
$( $(
discr_consts::$KindName => { discr_consts::$KindName => {
@ -333,7 +384,7 @@ macro_rules! proto_enum {
)* )*
} }
match self { $( <$len_ty>::default().byte_size() + )? match self {
$( $(
Self::$KindName $( { Self::$KindName $( {
$( $field_name, )* $( $field_name, )*
@ -342,7 +393,6 @@ macro_rules! proto_enum {
} }
)* )*
} }
} }
} }
}; };
@ -458,6 +508,49 @@ impl<T: Value, U: Value> Value for (T, U) {
} }
} }
#[derive(Debug, Clone, Copy, Default)]
#[allow(non_camel_case_types)]
struct u24(u32);
impl Value for u24 {
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.write_u24::<B>(self.0)
}
fn read<R: Read>(r: &mut R) -> crate::Result<Self> {
r.read_u24::<B>().map_err(Into::into).map(u24)
}
fn byte_size(&self) -> usize {
3
}
}
impl TryFrom<usize> for u24 {
type Error = TryFromIntError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
let value = u32::try_from(value)?;
if value > 2_u32.pow(24) {
return Err(u32::try_from(usize::MAX).unwrap_err());
}
Ok(u24(value))
}
}
struct MeasuringWriter<W>(usize, W);
impl<W: Write> Write for MeasuringWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let len = self.1.write(buf)?;
self.0 += len;
Ok(len)
}
fn flush(&mut self) -> io::Result<()> {
self.1.flush()
}
}
macro_rules! discard { macro_rules! discard {
($($tt:tt)*) => {}; ($($tt:tt)*) => {};
} }