diff --git a/src/crypt.rs b/src/crypt.rs index 69ca75f..2ec4d05 100644 --- a/src/crypt.rs +++ b/src/crypt.rs @@ -3,15 +3,19 @@ use crate::proto::{ self, ser_de::{proto_enum, proto_struct, Value}, + Handshake, }; 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 sha2::{ + digest::{ + core_api::{self, CoreProxy}, + generic_array::ArrayLength, + typenum::Unsigned, + OutputSizeUser, + }, + Digest, }; use x25519_dalek::SharedSecret; @@ -27,7 +31,7 @@ impl From> for TlsCiphertext { } } -trait HkdfHasher: OutputSizeUser { +trait TlsHasher: OutputSizeUser { const ZEROED: &'static [u8]; fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength>; fn extract(salt: &[u8], ikm: &[u8]) -> Vec; @@ -47,10 +51,10 @@ macro_rules! impl_hkdf_hasher { } }; } -impl HkdfHasher for sha2::Sha256 { +impl TlsHasher for sha2::Sha256 { impl_hkdf_hasher!(); } -impl HkdfHasher for sha2::Sha384 { +impl TlsHasher for sha2::Sha384 { impl_hkdf_hasher!(); } @@ -58,7 +62,7 @@ impl HkdfHasher for sha2::Sha384 { // 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 { +fn hkdf_expand_label(secret: &[u8], label: &[u8], context: &[u8]) -> Vec { proto_struct! { #[derive(Debug)] pub struct HkdfLabel { @@ -70,7 +74,7 @@ fn hkdf_expand_label(secret: &[u8], label: &[u8], context: &[u8]) let mut hkdf_label = Vec::new(); HkdfLabel { // Hash.length is its output length in bytes - length: H::output_size().try_into().unwrap(), + length: ::output_size().try_into().unwrap(), label: { let mut v = b"tls13 ".to_vec(); v.extend_from_slice(label); @@ -83,23 +87,23 @@ fn hkdf_expand_label(secret: &[u8], label: &[u8], context: &[u8]) let mut okm = [0u8; 128]; H::expand(secret, &hkdf_label, &mut okm).unwrap(); - okm[..H::output_size()].to_vec() + okm[..::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, &[]) +fn derive_secret(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec { + hkdf_expand_label::(secret, label, messages_hash) } 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, + derive_secret: fn(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec, } impl CryptoProvider { - fn new() -> Self { + fn new() -> Self { CryptoProvider { zeroed_of_hash_size: H::ZEROED, hkdf_extract: H::extract, @@ -108,6 +112,25 @@ impl CryptoProvider { } } +pub struct TranscriptHash { + state: sha2::Sha256, +} +impl TranscriptHash { + pub fn new() -> Self { + Self { + state: sha2::Sha256::new(), + } + } + pub fn handshake(&mut self, handshake: &Handshake) { + let mut buf = Vec::new(); + handshake.write(&mut buf); + self.state.update(&buf); + } + pub fn get_current(&self) -> Vec { + self.state.clone().finalize().to_vec() + } +} + /** ```text 0 @@ -159,53 +182,79 @@ impl CryptoProvider { = 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 early_secret = - (provider.hkdf_extract)(&provider.zeroed_of_hash_size, &provider.zeroed_of_hash_size); +pub struct KeysAfterServerHello { + provider: CryptoProvider, + handhske_secret: Vec, + pub client_handshake_traffic_secret: Vec, + pub server_handshake_traffic_secret: Vec, + master_secret: Vec, +} - let handhske_secret = (provider.hkdf_extract)( - &(provider.derive_secret)(&early_secret, b"derived", (/*empty*/)), - shared_secret.as_bytes(), - ); +impl KeysAfterServerHello { + pub fn compute( + shared_secret: SharedSecret, + algo: proto::CipherSuite, + transcript: &TranscriptHash, + ) -> Self { + let provider = match algo { + proto::CipherSuite::TLS_AES_128_GCM_SHA256 => CryptoProvider::new::(), + proto::CipherSuite::TLS_AES_256_GCM_SHA384 => { + todo!("anyhting but SHA256") + // 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 client_handshake_traffic_secret = (provider.derive_secret)( - &handhske_secret, - b"c hs traffic", - (/*clienthello..serverhello*/), - ); + let early_secret = + (provider.hkdf_extract)(&provider.zeroed_of_hash_size, &provider.zeroed_of_hash_size); - let server_handshake_traffic_secret = (provider.derive_secret)( - &handhske_secret, - b"s hs traffic", - (/*clienthello..serverhello*/), - ); + let handhske_secret = (provider.hkdf_extract)( + &(provider.derive_secret)(&early_secret, b"derived", &transcript.get_current()), + shared_secret.as_bytes(), + ); - let master_secret = (provider.hkdf_extract)( - &(provider.derive_secret)(&handhske_secret, b"derived", (/*empty*/)), - &provider.zeroed_of_hash_size, - ); + let client_handshake_traffic_secret = + (provider.derive_secret)(&handhske_secret, b"c hs traffic", &transcript.get_current()); - 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*/), - ); + let server_handshake_traffic_secret = + (provider.derive_secret)(&handhske_secret, b"s hs traffic", &transcript.get_current()); + let master_secret = (provider.hkdf_extract)( + &(provider.derive_secret)( + &handhske_secret, + b"derived", + &sha2::Sha256::new().finalize(), + ), + &provider.zeroed_of_hash_size, + ); - dbg!("keys"); + Self { + provider, + handhske_secret, + client_handshake_traffic_secret, + server_handshake_traffic_secret, + master_secret, + } + } + + fn after_handshake(self, transcript: &TranscriptHash) { + let client_application_traffic_secret_0 = (self.provider.derive_secret)( + &self.master_secret, + b"c ap traffic", + &transcript.get_current(), + ); + let server_application_traffic_secret_0 = (self.provider.derive_secret)( + &self.master_secret, + b"s ap traffic", + &transcript.get_current(), + ); + todo!() + } } pub struct AeadKey { @@ -264,13 +313,11 @@ fn proto_algo_to_ring(algo: proto::CipherSuite) -> &'static aead::Algorithm { } } -fn encrypt(key: AeadKey, message: &[u8], seq: u8) -> Vec { +fn encrypt(key: AeadKey, message: &[u8], seq: u8, nonce: Nonce) -> Vec { let total_len = message.len() + key.key.algorithm().tag_len(); let mut ciphertext_payload = Vec::with_capacity(total_len); ciphertext_payload.extend_from_slice(message); - // FIXME: dont use zero obviously - let nonce = aead::Nonce::assume_unique_for_key([0; aead::NONCE_LEN]); // FIXME: fill out the AAD properly let aad = aead::Aad::from([0; 5]); key.key @@ -280,6 +327,12 @@ fn encrypt(key: AeadKey, message: &[u8], seq: u8) -> Vec { ciphertext_payload } +fn decrypt(key: AeadKey, msg: &mut [u8], nonce: Nonce) { + // FIXME: fill out the AAD properly + let aad = aead::Aad::from([0; 5]); + key.key.open_in_place(nonce, aad, msg); +} + pub struct TlsInnerPlaintext { /// The `TLSPlaintext.fragment`` value pub content: Vec, @@ -289,7 +342,18 @@ pub struct TlsInnerPlaintext { } impl TlsCiphertext { - pub fn decrypt(&self, key: AeadKey, nonce: Nonce) -> TlsInnerPlaintext { - todo!() + pub fn decrypt(mut self, secret: &[u8], nonce: Nonce) -> TlsInnerPlaintext { + let key = hkdf_expand_label::(secret, b"key", &[]); + let iv = hkdf_expand_label::(secret, b"iv", &[]); + + let key = AeadKey::new(proto::CipherSuite::TLS_AES_128_GCM_SHA256, secret); + + decrypt(key, &mut self.encrypted_record, nonce); + + TlsInnerPlaintext { + content: self.encrypted_record, + content_type: 0, + padding_len: 0, + } } } diff --git a/src/lib.rs b/src/lib.rs index 9a06f9a..02523f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,8 @@ use std::{ io::{self, Read, Write}, }; -use proto::CipherSuite; +use crypt::{KeysAfterServerHello, TranscriptHash}; +use proto::{ser_de::Value, CipherSuite}; use crate::proto::TLSPlaintext; @@ -122,8 +123,11 @@ enum ConnectState { legacy_session_id: [u8; 32], secret: RefCell>, cipher_suites: Vec, + transcript: RefCell, + }, + WaitEncryptedExtensions { + keys: RefCell>, }, - WaitEncryptedExtensions, WaitCertificateRequest, WaitCertificate, WaitCertificateVerify, @@ -193,18 +197,24 @@ impl ClientSetupConnection { ] .into(), }; + + let mut transcript = TranscriptHash::new(); + transcript.handshake(&handshake); + let plaintext = proto::TLSPlaintext::Handshake { handshake }; self.stream.write_flush_record(plaintext)?; ConnectState::WaitServerHello { legacy_session_id, secret: RefCell::new(Some(secret)), cipher_suites, + transcript: RefCell::new(transcript), } } ConnectState::WaitServerHello { legacy_session_id, secret, cipher_suites, + transcript, } => { let (frame, seq_id) = self.stream.read_record()?; if frame.should_drop() { @@ -212,7 +222,7 @@ impl ClientSetupConnection { } let proto::TLSPlaintext::Handshake { handshake: - proto::Handshake::ServerHello { + handshake @ proto::Handshake::ServerHello { legacy_version, random, legacy_session_id_echo, @@ -220,7 +230,7 @@ impl ClientSetupConnection { legacy_compression_method, extensions, }, - } = frame + } = &frame else { return unexpected_message!("expected ServerHello, got {frame:?}"); }; @@ -229,7 +239,7 @@ impl ClientSetupConnection { return Err(ErrorKind::HelloRetryRequest.into()); } - if legacy_version != proto::LEGACY_TLSV12 { + if *legacy_version != proto::LEGACY_TLSV12 { return unexpected_message!( "unexpected TLS version in legacy_version field: {legacy_version:x?}" ); @@ -247,12 +257,14 @@ impl ClientSetupConnection { ); } - if legacy_compression_method != 0 { + if *legacy_compression_method != 0 { return unexpected_message!( "legacy compression method MUST be zero: {legacy_compression_method}" ); } + transcript.borrow_mut().handshake(&handshake); + let mut supported_versions = false; let mut server_key = None; @@ -305,11 +317,17 @@ impl ClientSetupConnection { dh_shared_secret.as_bytes() ); - crypt::compute_keys(dh_shared_secret, cipher_suite); + let keys = crypt::KeysAfterServerHello::compute( + dh_shared_secret, + *cipher_suite, + &transcript.borrow(), + ); - ConnectState::WaitEncryptedExtensions + ConnectState::WaitEncryptedExtensions { + keys: RefCell::new(Some(keys)), + } } - ConnectState::WaitEncryptedExtensions => { + ConnectState::WaitEncryptedExtensions { keys } => { let (frame, seq_id) = self.stream.read_record()?; if frame.should_drop() { continue; @@ -317,7 +335,15 @@ impl ClientSetupConnection { let proto::TLSPlaintext::ApplicationData { data } = frame else { return unexpected_message!("expected ApplicationData, got {frame:?}"); }; - //crypt::TlsCiphertext::from(data).decrypt(key, seq_id.to_nonce()); + // Encrypted with server_handshake_traffic_secret + crypt::TlsCiphertext::from(data).decrypt( + &keys + .borrow() + .as_ref() + .unwrap() + .server_handshake_traffic_secret, + seq_id.to_nonce(), + ); todo!() }