From c518c099377b463900a4fcb4014d7b4853a78929 Mon Sep 17 00:00:00 2001 From: Noratrieb <48135649+Noratrieb@users.noreply.github.com> Date: Sat, 3 Aug 2024 14:59:12 +0200 Subject: [PATCH] fix several bugs --- src/crypto/aead.rs | 41 ++++++++++++++++++++++---- src/crypto/keys.rs | 72 +++++++++++++++++++++++++++------------------ src/crypto/mod.rs | 15 ++++++---- src/lib.rs | 11 +++---- src/main.rs | 4 +-- src/proto/ser_de.rs | 5 +++- 6 files changed, 99 insertions(+), 49 deletions(-) diff --git a/src/crypto/aead.rs b/src/crypto/aead.rs index da1c603..47b4490 100644 --- a/src/crypto/aead.rs +++ b/src/crypto/aead.rs @@ -13,11 +13,32 @@ use super::{keys, TlsInnerPlaintext}; pub type AeadKey = aes_gcm::Key; pub type Nonce = aes_gcm::Nonce<::NonceSize>; -fn decrypt(key: &AeadKey, ciphertext: &[u8], nonce: &Nonce, additional_data: &[u8]) -> Vec { +pub struct TlsNonce { + nonce: Nonce, +} +impl TlsNonce { + pub fn new(nonce: Nonce) -> Self { + Self { nonce } + } + fn get_aead_nonce(mut self, iv: &[u8]) -> Nonce { + // + // 2. The padded sequence number is XORed with either the static + // client_write_iv or server_write_iv (depending on the role). + assert_eq!(self.nonce.len(), iv.len()); + self.nonce + .as_mut_slice() + .iter_mut() + .zip(iv) + .for_each(|(lhs, rhs)| *lhs ^= rhs); + self.nonce + } +} + +fn decrypt(key: &AeadKey, ciphertext: &[u8], nonce: Nonce, additional_data: &[u8]) -> Vec { let cipher = Aes128Gcm::new(key); cipher .decrypt( - nonce, + &nonce, Payload { msg: ciphertext, aad: additional_data, @@ -29,19 +50,27 @@ fn decrypt(key: &AeadKey, ciphertext: &[u8], nonce: &Nonce, additional_data: &[u pub fn decrypt_ciphertext( encrypted_record: &[u8], secret: &[u8], - nonce: Nonce, + nonce: TlsNonce, ) -> TlsInnerPlaintext { // - let key = keys::hkdf_expand_label::(secret, b"key", &[], 128/8); - + let key = keys::hkdf_expand_label(secret, b"key", &[], 128 / 8); let key = aes_gcm::Key::::from_slice(&key[0..(128 / 8)]); + let iv = keys::hkdf_expand_label(secret, b"iv", &[], 96 / 8); + + // // TLS v1.2 0x03, 0x03 let mut additional_data = [proto::TLSPlaintext::APPLICATION_DATA, 0x03, 0x03, 0, 0]; let ciphertext_len = encrypted_record.as_ref().len() as u16; additional_data[3..].copy_from_slice(&ciphertext_len.to_be_bytes()); - let result = decrypt(key, encrypted_record, &nonce, &additional_data); + // + let result = decrypt( + key, + encrypted_record, + nonce.get_aead_nonce(&iv), + &additional_data, + ); TlsInnerPlaintext { content: result, diff --git a/src/crypto/keys.rs b/src/crypto/keys.rs index 9455baa..a5d17d0 100644 --- a/src/crypto/keys.rs +++ b/src/crypto/keys.rs @@ -1,15 +1,16 @@ -use sha2::Digest; +use hkdf::Hkdf; +use sha2::{Digest, Sha256}; use x25519_dalek::SharedSecret; use crate::proto::{self, ser_de::Value}; -use super::{CryptoProvider, TlsHasher}; +use super::CryptoProvider; // 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 -pub(super) fn hkdf_expand_label( +pub(super) fn hkdf_expand_label( secret: &[u8], label: &[u8], context: &[u8], @@ -38,19 +39,18 @@ pub(super) fn hkdf_expand_label( .unwrap(); let mut okm = [0u8; 128]; - H::expand(secret, &hkdf_label, &mut okm).unwrap(); + hkdf::Hkdf::::from_prk(secret) + .unwrap() + .expand(&hkdf_label, &mut okm[..length]) + .unwrap(); okm[..length].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. -pub(super) fn derive_secret( - secret: &[u8], - label: &[u8], - messages_hash: &[u8], -) -> Vec { - hkdf_expand_label::(secret, label, messages_hash, H::output_size()) +pub(super) fn derive_secret(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec { + hkdf_expand_label(secret, label, messages_hash, Sha256::output_size()) } pub struct TranscriptHash { @@ -150,26 +150,40 @@ impl KeysAfterServerHello { 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); - - let handhske_secret = (provider.hkdf_extract)( - &(provider.derive_secret)(&early_secret, b"derived", &transcript.get_current()), - shared_secret.as_bytes(), + let (early_secret, _hkdf_use_this_its_nice) = hkdf::Hkdf::::extract( + Some(provider.zeroed_of_hash_size), + provider.zeroed_of_hash_size, ); - let client_handshake_traffic_secret = - (provider.derive_secret)(&handhske_secret, b"c hs traffic", &transcript.get_current()); + let early_secret_derived = + derive_secret(&early_secret, b"derived", &sha2::Sha256::new().finalize()); + println!("early_secret {:?}", early_secret); - let server_handshake_traffic_secret = - (provider.derive_secret)(&handhske_secret, b"s hs traffic", &transcript.get_current()); + println!("early_secret_derived {:?}", early_secret_derived); - let master_secret = (provider.hkdf_extract)( - &(provider.derive_secret)( - &handhske_secret, - b"derived", - &sha2::Sha256::new().finalize(), - ), + let (handshake_secret, _) = + Hkdf::::extract(Some(&early_secret_derived), shared_secret.as_bytes()); + + let client_handshake_traffic_secret = derive_secret( + &handshake_secret, + b"c hs traffic", + &transcript.get_current(), + ); + + let server_handshake_traffic_secret = derive_secret( + &handshake_secret, + b"s hs traffic", + &transcript.get_current(), + ); + + let handshake_secret_derived = derive_secret( + &handshake_secret, + b"derived", + &sha2::Sha256::new().finalize(), + ); + + let (master_secret, _) = Hkdf::::extract( + Some(&handshake_secret_derived), &provider.zeroed_of_hash_size, ); @@ -177,18 +191,18 @@ impl KeysAfterServerHello { provider, client_handshake_traffic_secret, server_handshake_traffic_secret, - master_secret, + master_secret: master_secret.to_vec(), } } #[allow(dead_code)] fn after_handshake(self, transcript: &TranscriptHash) { - let _client_application_traffic_secret_0 = (self.provider.derive_secret)( + let _client_application_traffic_secret_0 = derive_secret( &self.master_secret, b"c ap traffic", &transcript.get_current(), ); - let _server_application_traffic_secret_0 = (self.provider.derive_secret)( + let _server_application_traffic_secret_0 = derive_secret( &self.master_secret, b"s ap traffic", &transcript.get_current(), diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index bd0587c..6d68a96 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -36,14 +36,12 @@ impl TlsHasher for sha2::Sha384 { 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_hash: &[u8]) -> Vec, } impl CryptoProvider { fn new() -> Self { CryptoProvider { zeroed_of_hash_size: H::ZEROED, hkdf_extract: H::extract, - derive_secret: keys::derive_secret::, } } } @@ -51,7 +49,7 @@ impl CryptoProvider { mod seq { use std::cell::Cell; - use super::aead::Nonce; + use super::aead::{Nonce, TlsNonce}; /// The sequence ID generator. /// There is a separate one maintained for reading and writing. @@ -71,10 +69,15 @@ mod seq { // Don't implement `Clone` to ensure every seq id is only used once. pub struct SeqId(u64); impl SeqId { - pub fn to_nonce(self) -> Nonce { + pub fn to_nonce(self) -> TlsNonce { + // + // 1. The 64-bit record sequence number is encoded in network byte + // order and padded to the left with zeros to iv_length. let mut nonce = [0; 12]; - nonce[4..].copy_from_slice(&self.0.to_be_bytes()); - Nonce::from(nonce) + nonce[4..12].copy_from_slice(&self.0.to_be_bytes()); + let nonce = Nonce::from(nonce); + + TlsNonce::new(nonce) } } } diff --git a/src/lib.rs b/src/lib.rs index 40f5905..f06a43d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,10 +8,7 @@ use std::{ }; use crypto::keys::{KeysAfterServerHello, TranscriptHash}; -use proto::{ - ser_de::{FrameReader, Value}, - CipherSuite, TlsCiphertext, -}; +use proto::CipherSuite; use crate::proto::TLSPlaintext; @@ -149,6 +146,7 @@ impl ClientSetupConnection { let public = x25519_dalek::PublicKey::from(&secret); let legacy_session_id = rand::random::<[u8; 32]>(); + // Only AES-128 with SHA-256 let cipher_suites = vec![proto::CipherSuite::TLS_AES_128_GCM_SHA256]; let handshake = proto::Handshake::ClientHello { @@ -335,7 +333,10 @@ impl ClientSetupConnection { continue; } // Frame is a TLSCiphertext. - let proto::TLSPlaintext::ApplicationData { data: encrypted_record } = frame else { + let proto::TLSPlaintext::ApplicationData { + data: encrypted_record, + } = frame + else { return unexpected_message!("expected ApplicationData, got {frame:?}"); }; // Encrypted with server_handshake_traffic_secret diff --git a/src/main.rs b/src/main.rs index 2ae6152..41c8e76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,9 @@ use std::net::TcpStream; // An example program that makes a shitty HTTP/1.1 request. fn main() { - let conn = TcpStream::connect(("vps1.nilstrieb.dev", 443)) + let conn = TcpStream::connect(("noratrieb.dev", 443)) .unwrap() //.log() ; - tls::ClientConnection::establish(conn, "vps1.nilstrieb.dev").unwrap(); + tls::ClientConnection::establish(conn, "noratrieb.dev").unwrap(); } diff --git a/src/proto/ser_de.rs b/src/proto/ser_de.rs index 5c97751..56f0d42 100644 --- a/src/proto/ser_de.rs +++ b/src/proto/ser_de.rs @@ -270,7 +270,10 @@ impl + TryFrom + Default> Value for Li } impl + TryFrom + Default> List { - pub fn read_for_byte_length(mut remaining_byte_size: usize, r: &mut FrameReader) -> crate::Result { + pub fn read_for_byte_length( + mut remaining_byte_size: usize, + r: &mut FrameReader, + ) -> crate::Result { let mut v = Vec::new(); while remaining_byte_size > 0 { let value = T::read(r)?;