key: init

master
occheung 2020-10-16 17:38:29 +08:00
parent 912feac263
commit ec53973aec
9 changed files with 439 additions and 114 deletions

View File

@ -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"

View File

@ -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<usize>,
@ -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 {

View File

@ -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<Aes128, U16, U12>)
}
macro_rules! impl_cipher {
($($cipher_name: ident),+) => {
impl Cipher {
pub(crate) fn encrypt<T>(&self, rng: &mut T, associated_data: &[u8], buffer: &mut Vec<u8>) -> 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<T>(&self, rng: &mut T, associated_data: &[u8], buffer: &mut Vec<u8>) -> 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
);

226
src/cipher_suite.rs Normal file
View File

@ -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<Aes128, U16, U12>,
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::<Sha256>::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::<Sha256>::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::<Sha256>::new(None, &[0; 32])
// )
// },
// TLS_AES_256_GCM_SHA384 => {
// (
// sha_384,
// Sha384::new().chain(""),
// Hkdf::<Sha384>::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::<Sha256>::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::<Sha384>::new(
// Some(&derived_secret),
// ecdhe_shared.as_bytes()
// )
// }
// };
}
}
// macro_rules! impl_cipher {
// ($($cipher_name: ident),+) => {
// impl Cipher {
// pub(crate) fn encrypt<T>(&self, rng: &mut T, associated_data: &[u8], buffer: &mut Vec<u8>) -> 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<T>(&self, rng: &mut T, associated_data: &[u8], buffer: &mut Vec<u8>) -> 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
// );

111
src/key.rs Normal file
View File

@ -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<Hash>(
hkdf: &Hkdf<Hash>,
label: &str,
hash: Hash
) -> GenericArray<u8, Hash::OutputSize>
where
Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone,
Hash::OutputSize: ArrayLength<u8>,
{
// 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<U32> = 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<u8, Hash::OutputSize> = 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<Hash>(
hkdf: &Hkdf<Hash>,
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<U32> = 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();
}

View File

@ -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

0
src/session.rs Normal file
View File

View File

@ -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<R: 'static + RngCore + CryptoRng>
secret: RefCell<Option<EphemeralSecret>>, // Used enum Option to allow later init
session_id: RefCell<Option<[u8; 32]>>, // init session specific field later
received_change_cipher_spec: RefCell<Option<bool>>,
cipher: RefCell<Option<Cipher>>,
cipher: RefCell<Option<CipherSuite>>,
handshake_sha256: RefCell<Sha256>,
}
impl<R: RngCore + CryptoRng> TlsSocket<R> {
@ -82,6 +84,7 @@ impl<R: RngCore + CryptoRng> TlsSocket<R> {
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<R: RngCore + CryptoRng> TlsSocket<R> {
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<R: RngCore + CryptoRng> TlsSocket<R> {
// 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<R: RngCore + CryptoRng> TlsSocket<R> {
}
// 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<R: RngCore + CryptoRng> TlsSocket<R> {
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<R: RngCore + CryptoRng> TlsSocket<R> {
)
}
// 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::<TcpSocket>(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<Vec::<TlsRepr>> {

View File

@ -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()