diff --git a/Cargo.toml b/Cargo.toml index bd1085b..87c893d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" [dependencies] aes-gcm = "0.7.0" -chacha20poly1305 = "0.6.0" +ccm = "0.2.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 } @@ -23,21 +23,22 @@ version = "0.5.1" default-features = false features = [] +[dependencies.chacha20poly1305] +version = "0.6.0" +default-features = false +features = [ "alloc", "chacha20" ] + [dependencies.p256] version = "0.5.0" default-features = false features = [ "ecdh", "ecdsa", "arithmetic" ] +# Fetch from master, for "no_std" + "alloc" combination [dependencies.rsa] git = "https://github.com/RustCrypto/RSA.git" default-features = false features = [ "alloc" ] -[dependencies.heapless] -version = "0.5.6" -default-features = false -features = [] - [dependencies.nom] version = "5.1.2" default-features = false diff --git a/src/lib.rs b/src/lib.rs index d244e5d..94a3e72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,5 +11,7 @@ pub mod parse; // Details: Encapsulate smoltcp & nom errors pub enum Error { PropagatedError(smoltcp::Error), - ParsingError() + ParsingError, + EncryptionError, + CapacityError, } \ No newline at end of file diff --git a/src/tls.rs b/src/tls.rs index 5bee226..a4afa88 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -23,9 +23,15 @@ use core::cell::RefCell; use rand_core::{RngCore, CryptoRng}; 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 alloc::vec::{ self, Vec }; +use crate::Error as TlsError; use crate::tls_packet::*; use crate::parse::parse_tls_repr; @@ -42,6 +48,7 @@ enum TlsState { CONNECTED, } +// TODO: Group up all session_specific parameters into a separate structure pub struct TlsSocket { state: RefCell, @@ -49,10 +56,76 @@ pub struct TlsSocket rng: R, secret: RefCell>, // Used enum Option to allow later init session_id: RefCell>, // init session specific field later - cipher_suite: RefCell>, - ecdhe_shared: RefCell>, + received_change_cipher_spec: RefCell>, + cipher: RefCell>, } +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 +); + impl TlsSocket { pub fn new<'a, 'b, 'c>( sockets: &mut SocketSet<'a, 'b, 'c>, @@ -71,8 +144,8 @@ impl TlsSocket { rng, secret: RefCell::new(None), session_id: RefCell::new(None), - cipher_suite: RefCell::new(None), - ecdhe_shared: RefCell::new(None), + received_change_cipher_spec: RefCell::new(None), + cipher: RefCell::new(None), } } @@ -131,6 +204,7 @@ impl TlsSocket { // Store session settings, i.e. secret, session_id self.secret.replace(Some(ecdh_secret)); self.session_id.replace(Some(session_id)); + self.received_change_cipher_spec.replace(Some(false)); // Update the TLS state self.state.replace(TlsState::WAIT_SH); @@ -163,6 +237,18 @@ impl TlsSocket { // Process TLS ingress during handshake fn process(&self, repr: &TlsRepr) -> Result<()> { let state = self.state.clone().into_inner(); + + // Change_cipher_spec check: + // Must receive CCS before recv peer's FINISH message + // i.e. Must happen after START and before CONNECTED + // + // CCS message only exist for compatibility reason, + // Drop the message and update `received_change_cipher_spec` + if repr.is_change_cipher_spec() { + self.received_change_cipher_spec.replace(Some(true)); + return Ok(()) + } + match state { // During WAIT_SH for a TLS client, client should wait for ServerHello TlsState::WAIT_SH => { @@ -200,7 +286,7 @@ impl TlsSocket { todo!() } // Store the cipher suite - self.cipher_suite.replace(Some(server_hello.cipher_suite)); + let selected_cipher = server_hello.cipher_suite; if server_hello.compression_method != 0 { // Abort communciation todo!() @@ -236,18 +322,27 @@ impl TlsSocket { todo!() } // Store key - // It is surely from secp256r1 + // It is surely from secp256r1, no other groups are permitted // Convert untagged bytes into encoded point on p256 eliptic curve // Slice the first byte out of the bytes let server_public = EncodedPoint::from_untagged_bytes( GenericArray::from_slice(&server_share.key_exchange[1..]) ); // TODO: Handle improper shared key - self.ecdhe_shared.replace(Some( - self.secret.borrow().as_ref().unwrap() - .diffie_hellman(&server_public) - .expect("Unsupported key") - )); + // Right now is causes a panic, only socket abort is needed + let secret = self.secret.replace(None); + let shared = secret.unwrap() + .diffie_hellman(&server_public) + .expect("Unsupported key"); + let cipher = match selected_cipher { + CipherSuite::TLS_AES_256_GCM_SHA384 => { + Cipher::TLS_AES_256_GCM_SHA384( + Aes256Gcm::new(shared.as_bytes()) + ) + } + _ => todo!() + }; + self.cipher.replace(Some(cipher)); } } } @@ -259,7 +354,14 @@ impl TlsSocket { } } - } + }, + + // Expect encrypted extensions after receiving SH + TlsState::WAIT_EE => { + // ExcepytedExtensions are disguised as ApplicationData + // Pull out the `payload` from TlsRepr, decrypt as EE + }, + _ => {}, } diff --git a/src/tls_packet.rs b/src/tls_packet.rs index 82a90eb..e3ba204 100644 --- a/src/tls_packet.rs +++ b/src/tls_packet.rs @@ -5,7 +5,7 @@ use num_enum::TryFromPrimitive; use rand_core::RngCore; use rand_core::CryptoRng; -use p256::{EncodedPoint, AffinePoint, ecdh::EphemeralSecret}; +use p256::{EncodedPoint, AffinePoint, ecdh::{EphemeralSecret, SharedSecret}}; use core::convert::TryFrom; use core::convert::TryInto; @@ -90,6 +90,23 @@ impl<'a> TlsRepr<'a> { } } } + + pub(crate) fn is_change_cipher_spec(&self) -> bool { + self.content_type == TlsContentType::ChangeCipherSpec && + self.handshake.is_none() && + self.payload.is_some() && + { + if let Some(data) = self.payload { + [0x01] == data + } else { + false + } + } + } + + pub(crate) fn decrypt_ee(&self, shared_secret: &SharedSecret) -> HandshakeRepr { + todo!() + } } #[derive(Debug, PartialEq, Eq, Clone, Copy, IntoPrimitive, TryFromPrimitive)] @@ -164,6 +181,7 @@ pub(crate) enum HandshakeData<'a> { Uninitialized, ClientHello(ClientHello<'a>), ServerHello(ServerHello<'a>), + ExcryptedExtensions(EncryptedExtensions), } impl<'a> HandshakeData<'a> { @@ -383,6 +401,12 @@ pub(crate) struct ServerHello<'a> { pub(crate) extensions: Vec, } +#[derive(Debug, Clone)] +pub(crate) struct EncryptedExtensions { + length: u16, + extensions: Vec, +} + #[derive(Debug, PartialEq, Eq, Clone, Copy, IntoPrimitive, TryFromPrimitive)] #[repr(u16)] pub(crate) enum ExtensionType {