diff --git a/src/crypt.rs b/src/crypt.rs index b952827..69ca75f 100644 --- a/src/crypt.rs +++ b/src/crypt.rs @@ -4,8 +4,16 @@ use crate::proto::{ self, ser_de::{proto_enum, proto_struct, Value}, }; - +use hkdf::{hmac::Hmac, HmacImpl}; use ring::aead::{self, Nonce}; +pub use seq::{SeqId, SeqIdGen}; +use sha2::digest::{ + core_api::{self, CoreProxy}, + generic_array::ArrayLength, + typenum::Unsigned, + OutputSizeUser, +}; +use x25519_dalek::SharedSecret; pub struct TlsCiphertext { /// The encrypted [`TlsInnerPlaintext`] record. @@ -19,37 +27,185 @@ impl From> for TlsCiphertext { } } -pub fn compute_keys(shared_secret: SharedSecret) { - let hkdf_expand_label = |secret: &[u8], label: &[u8], context: &[u8], length| { - proto_struct! { - #[derive(Debug)] - pub struct HkdfLabel { - pub length: u16, - pub label: proto::ser_de::List, - pub context: proto::ser_de::List, - } +trait HkdfHasher: OutputSizeUser { + const ZEROED: &'static [u8]; + fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength>; + fn extract(salt: &[u8], ikm: &[u8]) -> Vec; +} +macro_rules! impl_hkdf_hasher { + () => { + const ZEROED: &'static [u8] = + &[0; <::OutputSize as Unsigned>::USIZE]; + fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength> { + hkdf::Hkdf::::new(None, ikm).expand(&label, okm) } - let mut hkdf_label = Vec::new(); - HkdfLabel { - length, - label: { - let mut v = b"tls13 ".to_vec(); - v.extend_from_slice(label); - v.into() - }, - context: context.to_vec().into(), + fn extract(salt: &[u8], ikm: &[u8]) -> Vec { + hkdf::Hkdf::::extract(Some(&[]), &[]) + .0 + .as_slice() + .to_vec() } - .write(&mut hkdf_label) - .unwrap(); + }; +} +impl HkdfHasher for sha2::Sha256 { + impl_hkdf_hasher!(); +} +impl HkdfHasher for sha2::Sha384 { + impl_hkdf_hasher!(); +} - // TODO: use correct algo, the cipher suite hash algorithm! - let mut okm = [0u8; 42]; - hkdf::Hkdf::::new(None, secret).expand(&hkdf_label, &mut okm) +// Key Schedule +// https://datatracker.ietf.org/doc/html/rfc8446#section-7.1 + +// The Hash function used by Transcript-Hash and HKDF is the cipher suite hash algorithm +fn hkdf_expand_label(secret: &[u8], label: &[u8], context: &[u8]) -> Vec { + proto_struct! { + #[derive(Debug)] + pub struct HkdfLabel { + pub length: u16, + pub label: proto::ser_de::List, + pub context: proto::ser_de::List, + } + } + let mut hkdf_label = Vec::new(); + HkdfLabel { + // Hash.length is its output length in bytes + length: H::output_size().try_into().unwrap(), + label: { + let mut v = b"tls13 ".to_vec(); + v.extend_from_slice(label); + v.into() + }, + context: context.to_vec().into(), + } + .write(&mut hkdf_label) + .unwrap(); + + let mut okm = [0u8; 128]; + H::expand(secret, &hkdf_label, &mut okm).unwrap(); + okm[..H::output_size()].to_vec() +} + +/// Messages is the concatenation of the indicated handshake messages, +/// including the handshake message type and length fields, but not +/// including record layer headers. +fn derive_secret(secret: &[u8], label: &[u8], messages: ()) -> Vec { + hkdf_expand_label::(secret, label, &[]) +} + +pub struct CryptoProvider { + zeroed_of_hash_size: &'static [u8], + hkdf_extract: fn(salt: &[u8], ikm: &[u8]) -> Vec, + derive_secret: fn(secret: &[u8], label: &[u8], messages: ()) -> Vec, +} +impl CryptoProvider { + fn new() -> Self { + CryptoProvider { + zeroed_of_hash_size: H::ZEROED, + hkdf_extract: H::extract, + derive_secret: derive_secret::, + } + } +} + +/** +```text + 0 + | + v + PSK -> HKDF-Extract = Early Secret + | + +-----> Derive-Secret(., "ext binder" | "res binder", "") + | = binder_key + | + +-----> Derive-Secret(., "c e traffic", ClientHello) + | = client_early_traffic_secret + | + +-----> Derive-Secret(., "e exp master", ClientHello) + | = early_exporter_master_secret + v + Derive-Secret(., "derived", "") + | + v + (EC)DHE -> HKDF-Extract = Handshake Secret + | + +-----> Derive-Secret(., "c hs traffic", + | ClientHello...ServerHello) + | = client_handshake_traffic_secret + | + +-----> Derive-Secret(., "s hs traffic", + | ClientHello...ServerHello) + | = server_handshake_traffic_secret + v + Derive-Secret(., "derived", "") + | + v + 0 -> HKDF-Extract = Master Secret + | + +-----> Derive-Secret(., "c ap traffic", + | ClientHello...server Finished) + | = client_application_traffic_secret_0 + | + +-----> Derive-Secret(., "s ap traffic", + | ClientHello...server Finished) + | = server_application_traffic_secret_0 + | + +-----> Derive-Secret(., "exp master", + | ClientHello...server Finished) + | = exporter_master_secret + | + +-----> Derive-Secret(., "res master", + ClientHello...client Finished) + = resumption_master_secret +``` +*/ +pub fn compute_keys(shared_secret: SharedSecret, algo: proto::CipherSuite) { + let provider = match algo { + proto::CipherSuite::TLS_AES_128_GCM_SHA256 => CryptoProvider::new::(), + proto::CipherSuite::TLS_AES_256_GCM_SHA384 => CryptoProvider::new::(), + proto::CipherSuite::TLS_CHACHA20_POLY1305_SHA256 => CryptoProvider::new::(), + proto::CipherSuite::TLS_AES_128_CCM_SHA256 => CryptoProvider::new::(), + proto::CipherSuite::TLS_AES_128_CCM_8_SHA256 => CryptoProvider::new::(), }; - let derive_secret = |secret: &[u8], label: &[u8], messages: ()| { - hkdf_expand_label(secret, label, &[], 16) // todo: fix length - }; + let early_secret = + (provider.hkdf_extract)(&provider.zeroed_of_hash_size, &provider.zeroed_of_hash_size); + + let handhske_secret = (provider.hkdf_extract)( + &(provider.derive_secret)(&early_secret, b"derived", (/*empty*/)), + shared_secret.as_bytes(), + ); + + let client_handshake_traffic_secret = (provider.derive_secret)( + &handhske_secret, + b"c hs traffic", + (/*clienthello..serverhello*/), + ); + + let server_handshake_traffic_secret = (provider.derive_secret)( + &handhske_secret, + b"s hs traffic", + (/*clienthello..serverhello*/), + ); + + let master_secret = (provider.hkdf_extract)( + &(provider.derive_secret)(&handhske_secret, b"derived", (/*empty*/)), + &provider.zeroed_of_hash_size, + ); + + let client_application_traffic_secret_0 = (provider.derive_secret)( + &master_secret, + b"c ap traffic", + (/*clienthello..server finished*/), + ); + let server_application_traffic_secret_0 = (provider.derive_secret)( + &master_secret, + b"s ap traffic", + (/*clienthello..server finished*/), + ); + + + dbg!("keys"); } pub struct AeadKey { @@ -97,8 +253,6 @@ mod seq { } } } -pub use seq::{SeqId, SeqIdGen}; -use x25519_dalek::SharedSecret; fn proto_algo_to_ring(algo: proto::CipherSuite) -> &'static aead::Algorithm { match algo { diff --git a/src/lib.rs b/src/lib.rs index 66f072d..9a06f9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + mod crypt; pub mod proto; @@ -7,7 +9,6 @@ use std::{ io::{self, Read, Write}, }; -use crypt::{SeqId, SeqIdGen}; use proto::CipherSuite; use crate::proto::TLSPlaintext; @@ -61,7 +62,6 @@ mod stream_state { pub fn write_record(&mut self, plaintext: TLSPlaintext) -> Result { plaintext.write(&mut self.stream)?; - self.stream.flush()?; Ok(self.write_seq_id.next()) } @@ -305,7 +305,7 @@ impl ClientSetupConnection { dh_shared_secret.as_bytes() ); - crypt::compute_keys(dh_shared_secret); + crypt::compute_keys(dh_shared_secret, cipher_suite); ConnectState::WaitEncryptedExtensions } @@ -396,3 +396,10 @@ impl io::Read for LoggingWriter { len } } + +pub trait LoggingWriterExt: Sized { + fn log(self) -> LoggingWriter { + LoggingWriter(self) + } +} +impl LoggingWriterExt for W {} diff --git a/src/main.rs b/src/main.rs index 2da4619..2ae6152 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,9 @@ use std::net::TcpStream; // An example program that makes a shitty HTTP/1.1 request. fn main() { - let conn = tls::LoggingWriter(TcpStream::connect(("vps1.nilstrieb.dev", 443)).unwrap()); + let conn = TcpStream::connect(("vps1.nilstrieb.dev", 443)) + .unwrap() + //.log() + ; tls::ClientConnection::establish(conn, "vps1.nilstrieb.dev").unwrap(); } diff --git a/src/proto/ser_de.rs b/src/proto/ser_de.rs index 2284c4a..5c97751 100644 --- a/src/proto/ser_de.rs +++ b/src/proto/ser_de.rs @@ -106,7 +106,7 @@ macro_rules! proto_enum { impl crate::proto::ser_de::Value for $name { fn write(&self, w: &mut W) -> io::Result<()> { w.flush()?; - eprintln!("{}", stringify!($name)); + //eprintln!("{}", stringify!($name)); mod discr_consts { $( #[allow(non_upper_case_globals)] @@ -116,7 +116,7 @@ macro_rules! proto_enum { let write_len = |_w: &mut W, _len: usize| -> io::Result<()> { _w.flush()?; - eprintln!("length"); + //eprintln!("length"); $( <$len_ty>::try_from(_len).unwrap().write(_w)?; )? @@ -137,7 +137,7 @@ macro_rules! proto_enum { $($( w.flush()?; - eprintln!("{}", stringify!($field_name)); + //eprintln!("{}", stringify!($field_name)); crate::proto::ser_de::Value::write($field_name, w)?; )*)?