diff --git a/Cargo.toml b/Cargo.toml index 87c893d..06b8358 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,13 @@ edition = "2018" [dependencies] aes-gcm = "0.7.0" ccm = "0.2.0" +hkdf = "0.9.0" sha2 = { version = "0.9.1", default-features = false } byteorder = { version = "1.3.4", default-features = false } num_enum = { version = "0.5.1", default-features = false } log = "0.4.11" generic-array = "0.14.4" +heapless = "0.5.6" [dependencies.smoltcp] version = "0.6.0" diff --git a/src/buffer.rs b/src/buffer.rs index e452d8d..efb1109 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -8,8 +8,10 @@ use alloc::vec::Vec; use byteorder::{ByteOrder, NetworkEndian, BigEndian}; use crate::tls_packet::*; +use crate::key::*; // Only designed to support read or write the entire buffer +// TODO: Stricter visibility pub(crate) struct TlsBuffer<'a> { buffer: &'a mut [u8], index: RefCell, @@ -22,7 +24,7 @@ impl<'a> Into<&'a [u8]> for TlsBuffer<'a> { } impl<'a> TlsBuffer<'a> { - pub(crate) fn new(buffer: &'a mut [u8]) -> Self { + pub fn new(buffer: &'a mut [u8]) -> Self { Self { buffer, index: RefCell::new(0), @@ -199,6 +201,14 @@ impl<'a> TlsBuffer<'a> { self.write_u16(entry.length)?; self.write(entry.key_exchange.as_slice()) } + + pub fn enqueue_hkdf_label(&mut self, hkdf_label: HkdfLabel) -> Result<()> { + self.write_u16(hkdf_label.length)?; + self.write_u8(hkdf_label.label_length)?; + self.write(hkdf_label.label)?; + self.write_u8(hkdf_label.context_length)?; + self.write(hkdf_label.context) + } } macro_rules! export_byte_order_fn { diff --git a/src/cipher.rs b/src/cipher.rs deleted file mode 100644 index c72a68b..0000000 --- a/src/cipher.rs +++ /dev/null @@ -1,76 +0,0 @@ -use p256::{EncodedPoint, AffinePoint, ecdh::EphemeralSecret, ecdh::SharedSecret}; -use aes_gcm::{Aes128Gcm, Aes256Gcm}; -use chacha20poly1305::{ChaCha20Poly1305, Key}; -use ccm::{Ccm, consts::*}; -use aes_gcm::aes::Aes128; -use aes_gcm::{AeadInPlace, NewAead}; -use generic_array::GenericArray; -use rand_core::{ RngCore, CryptoRng }; -use alloc::vec::Vec; -use crate::Error as TlsError; - -pub(crate) enum Cipher { - TLS_AES_128_GCM_SHA256(Aes128Gcm), - TLS_AES_256_GCM_SHA384(Aes256Gcm), - TLS_CHACHA20_POLY1305_SHA256(ChaCha20Poly1305), - TLS_AES_128_CCM_SHA256(Ccm) -} - -macro_rules! impl_cipher { - ($($cipher_name: ident),+) => { - impl Cipher { - pub(crate) fn encrypt(&self, rng: &mut T, associated_data: &[u8], buffer: &mut Vec) -> core::result::Result<(), TlsError> - where - T: RngCore + CryptoRng - { - // All 4 supported Ciphers use a nonce of 12 bytes - let mut nonce_array: [u8; 12] = [0; 12]; - rng.fill_bytes(&mut nonce_array); - use Cipher::*; - match self { - $( - $cipher_name(cipher) => { - cipher.encrypt_in_place( - &GenericArray::from_slice(&nonce_array), - associated_data, - buffer - ).map_err( - |_| TlsError::EncryptionError - ) - } - )+ - } - } - - pub(crate) fn decrypt(&self, rng: &mut T, associated_data: &[u8], buffer: &mut Vec) -> core::result::Result<(), TlsError> - where - T: RngCore + CryptoRng - { - // All 4 supported Ciphers use a nonce of 12 bytes - let mut nonce_array: [u8; 12] = [0; 12]; - rng.fill_bytes(&mut nonce_array); - use Cipher::*; - match self { - $( - $cipher_name(cipher) => { - cipher.decrypt_in_place( - &GenericArray::from_slice(&nonce_array), - associated_data, - buffer - ).map_err( - |_| TlsError::EncryptionError - ) - } - )+ - } - } - } - } -} - -impl_cipher!( - TLS_AES_128_GCM_SHA256, - TLS_AES_256_GCM_SHA384, - TLS_CHACHA20_POLY1305_SHA256, - TLS_AES_128_CCM_SHA256 -); \ No newline at end of file diff --git a/src/cipher_suite.rs b/src/cipher_suite.rs new file mode 100644 index 0000000..59263d4 --- /dev/null +++ b/src/cipher_suite.rs @@ -0,0 +1,226 @@ +use p256::{EncodedPoint, AffinePoint, ecdh::EphemeralSecret, ecdh::SharedSecret}; +use aes_gcm::{Aes128Gcm, Aes256Gcm}; +use chacha20poly1305::{ChaCha20Poly1305, Key}; +use ccm::{Ccm, consts::*}; +use aes_gcm::aes::Aes128; +use aes_gcm::{AeadInPlace, NewAead}; +use generic_array::GenericArray; +use rand_core::{ RngCore, CryptoRng }; +use sha2::{ Digest, Sha256, Sha384, Sha512 }; +use heapless::Vec; +use hkdf::Hkdf; + +use crate::Error as TlsError; +use crate::tls_packet::CipherSuite as CipherSuiteField; +use crate::key::*; + +// A structure representing the block cipher and the hashes +pub(crate) enum CipherSuite { + // Handshake is still proceeding, no cipher can be produced yet, + // Though hashes still has to be prepared for deriving key later, + // This enum offers all possible hashes that could be needed + // i.e. SHA256 and SHA384 + Undetermined { + sha_256: Sha256, + sha_384: Sha384, + }, + + // Established cipher suites + // Contains a block cipher (GCM/CCM/ChaChaPoly) + // Contains a hash function (SHA256/SHA384) + TLS_AES_128_GCM_SHA256 { + aes_128_gcm: Aes128Gcm, + sha_256: Sha256, + }, + TLS_AES_256_GCM_SHA384 { + aes_256_gcm: Aes256Gcm, + sha_384: Sha384, + }, + TLS_CHACHA20_POLY1305_SHA256 { + chacha20_poly1305: ChaCha20Poly1305, + sha_256: Sha256, + }, + TLS_AES_128_CCM_SHA256 { + ccm: Ccm, + sha_256: Sha256, + }, +} + + +impl CipherSuite { + pub(crate) fn new() -> Self { + CipherSuite::Undetermined { + sha_256: Sha256::new(), + sha_384: Sha384::new(), + } + } + + // Assume no PSK, establish ciphersuite along side handshake secret + // Need to update hash function before calling + pub(crate) fn establish( + self, + field: CipherSuiteField, + ecdhe_shared: SharedSecret + ) -> Self { + use CipherSuiteField::*; + + let (sha_256, sha_384) = { + if let CipherSuite::Undetermined { + sha_256, + sha_384, + } = self { + (sha_256, sha_384) + } else { + // TODO: Implement key change + return self; + } + }; + + match field { + TLS_AES_128_GCM_SHA256 | TLS_CHACHA20_POLY1305_SHA256 | + TLS_AES_128_CCM_SHA256 => { + // Compute early_secret in HKDF, without PSK + let empty_hash = Sha256::new().chain(""); + let early_secret = Hkdf::::new(None, &[0; 32]); + + // Calculate derived secret + let derived_secret = derive_secret( + &early_secret, + "derived", + empty_hash + ); + + // Calculate handshake secret in HKDF + let handshake_secret = Hkdf::::new( + Some(&derived_secret), + ecdhe_shared.as_bytes() + ); + + // Calculate client_handshake_traffic_secret + let client_handshake_traffic_secret = derive_secret( + &handshake_secret, + "c hs traffic", + sha_256.clone() + ); + + // Calculate server_handshake_traffic_secret + let server_handshake_traffic_secret = derive_secret( + &handshake_secret, + "c hs traffic", + sha_256.clone() + ); + + // let client_write_key = hkdf_expand_label( + // + // ); + } + _ => todo!() + } + + todo!() + + // // Compute HKDF + // let (hash, empty_hash, hkdf) = match field { + // TLS_AES_128_GCM_SHA256 | + // TLS_CHACHA20_POLY1305_SHA256 | + // TLS_AES_128_CCM_SHA256 => { + // ( + // sha_256, + // Sha256::new().chain(""), + // Hkdf::::new(None, &[0; 32]) + // ) + // }, + // TLS_AES_256_GCM_SHA384 => { + // ( + // sha_384, + // Sha384::new().chain(""), + // Hkdf::::new(None, &[0; 48]) + // ) + // } + // }; + + // // get_derived_secret, then insert ECDHE shared secret + // let derived_secret = derive_secret(hkdf, "derived", empty_hash); + + // let (key, iv) = match field { + // TLS_AES_128_GCM_SHA256 | + // TLS_CHACHA20_POLY1305_SHA256 | + // TLS_AES_128_CCM_SHA256 => { + // let hkdf = Hkdf::::new( + // Some(&derived_secret), + // ecdhe_shared.as_bytes() + // ); + // let client_handshake_traffic_secret = derive_secret( + // hkdf, + // "c hs traffic", + // sha256.clone(), + // ); + // }, + // TLS_AES_256_GCM_SHA384 => { + // Hkdf::::new( + // Some(&derived_secret), + // ecdhe_shared.as_bytes() + // ) + // } + // }; + } +} + +// macro_rules! impl_cipher { +// ($($cipher_name: ident),+) => { +// impl Cipher { +// pub(crate) fn encrypt(&self, rng: &mut T, associated_data: &[u8], buffer: &mut Vec) -> core::result::Result<(), TlsError> +// where +// T: RngCore + CryptoRng +// { +// // All 4 supported Ciphers use a nonce of 12 bytes +// let mut nonce_array: [u8; 12] = [0; 12]; +// rng.fill_bytes(&mut nonce_array); +// use Cipher::*; +// match self { +// $( +// $cipher_name(cipher) => { +// cipher.encrypt_in_place( +// &GenericArray::from_slice(&nonce_array), +// associated_data, +// buffer +// ).map_err( +// |_| TlsError::EncryptionError +// ) +// } +// )+ +// } +// } + +// pub(crate) fn decrypt(&self, rng: &mut T, associated_data: &[u8], buffer: &mut Vec) -> core::result::Result<(), TlsError> +// where +// T: RngCore + CryptoRng +// { +// // All 4 supported Ciphers use a nonce of 12 bytes +// let mut nonce_array: [u8; 12] = [0; 12]; +// rng.fill_bytes(&mut nonce_array); +// use Cipher::*; +// match self { +// $( +// $cipher_name(cipher) => { +// cipher.decrypt_in_place( +// &GenericArray::from_slice(&nonce_array), +// associated_data, +// buffer +// ).map_err( +// |_| TlsError::EncryptionError +// ) +// } +// )+ +// } +// } +// } +// } +// } + +// impl_cipher!( +// TLS_AES_128_GCM_SHA256, +// TLS_AES_256_GCM_SHA384, +// TLS_CHACHA20_POLY1305_SHA256, +// TLS_AES_128_CCM_SHA256 +// ); \ No newline at end of file diff --git a/src/key.rs b/src/key.rs new file mode 100644 index 0000000..b311dfe --- /dev/null +++ b/src/key.rs @@ -0,0 +1,111 @@ +use hkdf::Hkdf; +use sha2::{ Digest, Sha256, Sha384, Sha512 }; +use sha2::digest::{BlockInput, FixedOutput, Reset, Update}; +use generic_array::{ GenericArray, ArrayLength }; +use heapless::{ String, Vec, consts::* }; + +use crate::buffer::TlsBuffer; + +use core::convert::TryFrom; + +#[derive(Debug, Clone)] +pub(crate) struct HkdfLabel<'a> { + // Length of hash function + pub(crate) length: u16, + // Label vector: "tls13 " + label + pub(crate) label_length: u8, + pub(crate) label: &'a [u8], + // Context vector: Hashed message + pub(crate) context_length: u8, + pub(crate) context: &'a [u8], +} + +// Implementation of Derive-Secret function in RFC8446 +pub(crate) fn derive_secret( + hkdf: &Hkdf, + label: &str, + hash: Hash +) -> GenericArray +where + Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone, + Hash::OutputSize: ArrayLength, +{ + // Build a string using heapless + // label size: + // prefix: "tls13 " => 6 chars + // suffix: at most 12 chars as per RFC8446, section 7.1 + let mut label_string: String = String::new(); + label_string.push_str("tls13 ").unwrap(); + label_string.push_str(label); + + let length = u16::try_from(Hash::output_size()).unwrap(); + let label_length = u8::try_from(label_string.len()).unwrap(); + + let hkdf_label = HkdfLabel { + length, + label_length, + label: label_string.as_ref(), + context_length: u8::try_from(length).unwrap(), + context: &hash.finalize(), + }; + + // Build info from HKDF label using Buffer + // length: 2 bytes, + // label_vec: 18 bytes (label) + 1 byte (len) + // context_vec: 48 bytes for SHA384 + 1 byte (len) + let mut array = [0; 100]; + let mut buffer = TlsBuffer::new(&mut array); + buffer.enqueue_hkdf_label(hkdf_label); + let info: &[u8] = buffer.into(); + + // Define output key material (OKM), dynamically sized by hash + let mut okm: GenericArray = GenericArray::default(); + hkdf.expand(info, &mut okm).unwrap(); + okm +} + +// Implementation of HKDF-Expand-Label function in RFC8446 +// Secret is embedded inside hkdf through salt and input key material (IKM) +pub(crate) fn hkdf_expand_label( + hkdf: &Hkdf, + label: &str, + context: &str, + okm: &mut [u8], +) +where + Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone, +{ + // Build a string using heapless + // label size: + // prefix: "tls13 " => 6 chars + // suffix: at most 12 chars as per RFC8446, section 7.1 + let mut label_string: String = String::new(); + label_string.push_str("tls13 ").unwrap(); + label_string.push_str(label); + let label_length = u8::try_from(label_string.len()).unwrap(); + + let context_slice = context.as_bytes(); + let context_length = u8::try_from(context_slice.len()).unwrap(); + + let length = u16::try_from(okm.len()).unwrap(); + + // Build HKDF label + let hkdf_label = HkdfLabel { + length, + label_length, + label: label_string.as_ref(), + context_length: context_length, + context: context_slice, + }; + + // Build info from HKDF label using Buffer + // length: 2 bytes, + // label_vec: 18 bytes (label) + 1 byte (len) + // context_vec: 48 bytes for SHA384 + 1 byte (len) + let mut array = [0; 100]; + let mut buffer = TlsBuffer::new(&mut array); + buffer.enqueue_hkdf_label(hkdf_label); + let info: &[u8] = buffer.into(); + + hkdf.expand(info, okm).unwrap(); +} diff --git a/src/lib.rs b/src/lib.rs index 2d37ced..1b6ecdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,8 +6,9 @@ extern crate alloc; pub mod tls; pub mod tls_packet; pub mod parse; -pub mod cipher; +pub mod cipher_suite; pub mod buffer; +pub mod key; // TODO: Implement errors // Details: Encapsulate smoltcp & nom errors diff --git a/src/session.rs b/src/session.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/tls.rs b/src/tls.rs index a7249dd..991c23a 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -28,13 +28,14 @@ use chacha20poly1305::{ChaCha20Poly1305, Key}; use ccm::{Ccm, consts::*}; use aes_gcm::aes::Aes128; use aes_gcm::{AeadInPlace, NewAead}; +use sha2::{Sha256, Sha384, Sha512, Digest}; use alloc::vec::{ self, Vec }; use crate::Error as TlsError; use crate::tls_packet::*; use crate::parse::parse_tls_repr; -use crate::cipher::Cipher; +use crate::cipher_suite::CipherSuite; use crate::buffer::TlsBuffer; #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -59,7 +60,8 @@ pub struct TlsSocket secret: RefCell>, // Used enum Option to allow later init session_id: RefCell>, // init session specific field later received_change_cipher_spec: RefCell>, - cipher: RefCell>, + cipher: RefCell>, + handshake_sha256: RefCell, } impl TlsSocket { @@ -82,6 +84,7 @@ impl TlsSocket { session_id: RefCell::new(None), received_change_cipher_spec: RefCell::new(None), cipher: RefCell::new(None), + handshake_sha256: RefCell::new(Sha256::new()), } } @@ -135,7 +138,19 @@ impl TlsSocket { self.rng.fill_bytes(&mut session_id); let repr = TlsRepr::new() .client_hello(&ecdh_secret, random, session_id); - self.send_tls_repr(sockets, repr)?; + + // Update hash function with client hello handshake + let mut array = [0; 2048]; + let mut buffer = TlsBuffer::new(&mut array); + buffer.enqueue_tls_repr(repr)?; + let slice: &[u8] = buffer.into(); + + // Update hash by handshake + // Update with entire packet except the record layer + { + self.handshake_sha256.borrow_mut().update(&slice[5..]); + } + self.send_tls_slice(sockets, slice)?; // Store session settings, i.e. secret, session_id self.secret.replace(Some(ecdh_secret)); @@ -161,9 +176,13 @@ impl TlsSocket { // Read for TLS packet let mut array: [u8; 2048] = [0; 2048]; - let tls_repr_vec = self.recv_tls_repr(sockets, &mut array)?; + let mut tls_repr_vec = self.recv_tls_repr(sockets, &mut array)?; - for repr in tls_repr_vec.iter() { + // Take the TLS representation out of the vector, + // Process as a queue + let tls_repr_vec_size = tls_repr_vec.len(); + for index in 0..tls_repr_vec_size { + let repr = tls_repr_vec.remove(0); self.process(repr)?; } @@ -171,7 +190,7 @@ impl TlsSocket { } // Process TLS ingress during handshake - fn process(&self, repr: &TlsRepr) -> Result<()> { + fn process(&self, repr: TlsRepr) -> Result<()> { let state = self.state.clone().into_inner(); // Change_cipher_spec check: @@ -270,44 +289,58 @@ impl TlsSocket { let shared = secret.unwrap() .diffie_hellman(&server_public) .expect("Unsupported key"); - let cipher = match selected_cipher { - CipherSuite::TLS_AES_128_GCM_SHA256 => { - Cipher::TLS_AES_128_GCM_SHA256( - todo!() - ) - }, - CipherSuite::TLS_AES_256_GCM_SHA384 => { - Cipher::TLS_AES_256_GCM_SHA384( - Aes256Gcm::new(shared.as_bytes()) - ) - }, - CipherSuite::TLS_CHACHA20_POLY1305_SHA256 => { - Cipher::TLS_CHACHA20_POLY1305_SHA256( - ChaCha20Poly1305::new(shared.as_bytes()) - ) - }, - CipherSuite::TLS_AES_128_CCM_SHA256 => { - Cipher::TLS_AES_128_CCM_SHA256( - todo!() - ) - }, - // CCM_8 is not supported - // TODO: Abort communication - CipherSuite::TLS_AES_128_CCM_8_SHA256 => { - todo!() - } - }; - self.cipher.replace(Some(cipher)); + // let cipher = match selected_cipher { + // CipherSuite::TLS_AES_128_GCM_SHA256 => { + // Cipher::TLS_AES_128_GCM_SHA256( + // todo!() + // ) + // }, + // CipherSuite::TLS_AES_256_GCM_SHA384 => { + // Cipher::TLS_AES_256_GCM_SHA384( + // Aes256Gcm::new(shared.as_bytes()) + // ) + // }, + // CipherSuite::TLS_CHACHA20_POLY1305_SHA256 => { + // Cipher::TLS_CHACHA20_POLY1305_SHA256( + // ChaCha20Poly1305::new(shared.as_bytes()) + // ) + // }, + // CipherSuite::TLS_AES_128_CCM_SHA256 => { + // Cipher::TLS_AES_128_CCM_SHA256( + // todo!() + // ) + // }, + // // CCM_8 is not supported + // // TODO: Abort communication + // CipherSuite::TLS_AES_128_CCM_8_SHA256 => { + // todo!() + // } + // }; + // self.cipher.replace(Some(cipher)); } } } - self.state.replace(TlsState::WAIT_EE); } else { // Handle invalid TLS packet todo!() } + // This is indeed a desirable ServerHello TLS repr + // Reprocess ServerHello into a slice + // Update SHA256 hasher with the slice + let mut array = [0; 2048]; + let mut buffer = TlsBuffer::new(&mut array); + buffer.enqueue_tls_repr(repr); + let slice: &[u8] = buffer.into(); + { + self.handshake_sha256 + .borrow_mut() + .update(&slice[5..]); + } + + // Update TLS session state + self.state.replace(TlsState::WAIT_EE); } }, @@ -343,6 +376,23 @@ impl TlsSocket { ) } + // Generic inner send method for buffer IO, through TCP socket + fn send_tls_slice(&self, sockets: &mut SocketSet, slice: &[u8]) -> Result<()> { + let mut tcp_socket = sockets.get::(self.tcp_handle); + if !tcp_socket.can_send() { + return Err(Error::Illegal); + } + let buffer_size = slice.len(); + tcp_socket.send_slice(slice) + .and_then( + |size| if size == buffer_size { + Ok(()) + } else { + Err(Error::Truncated) + } + ) + } + // Generic inner recv method, through TCP socket // A TCP packet can contain multiple TLS segments fn recv_tls_repr<'a>(&'a self, sockets: &mut SocketSet, byte_array: &'a mut [u8]) -> Result> { diff --git a/src/tls_packet.rs b/src/tls_packet.rs index cac21c2..553749e 100644 --- a/src/tls_packet.rs +++ b/src/tls_packet.rs @@ -201,7 +201,7 @@ impl<'a> ClientHello<'a> { random, session_id_length: 32, session_id, - cipher_suites_length: 6, + cipher_suites_length: 0, cipher_suites: &[ CipherSuite::TLS_AES_128_GCM_SHA256, CipherSuite::TLS_AES_256_GCM_SHA384, @@ -213,6 +213,7 @@ impl<'a> ClientHello<'a> { extension_length: 0, extensions: Vec::new(), }; + client_hello.cipher_suites_length = u16::try_from(client_hello.cipher_suites.len() * 2).unwrap(); client_hello.add_ch_supported_versions() .add_sig_algs()