This commit is contained in:
nora 2024-07-13 15:09:44 +02:00
parent eea717987d
commit c767352e43
4 changed files with 200 additions and 36 deletions

View file

@ -4,8 +4,16 @@ use crate::proto::{
self,
ser_de::{proto_enum, proto_struct, Value},
};
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 x25519_dalek::SharedSecret;
pub struct TlsCiphertext {
/// The encrypted [`TlsInnerPlaintext`] record.
@ -19,37 +27,185 @@ impl From<Vec<u8>> for TlsCiphertext {
}
}
pub fn compute_keys(shared_secret: SharedSecret) {
let hkdf_expand_label = |secret: &[u8], label: &[u8], context: &[u8], length| {
proto_struct! {
#[derive(Debug)]
pub struct HkdfLabel {
pub length: u16,
pub label: proto::ser_de::List<u8,u8>,
pub context: proto::ser_de::List<u8,u8>,
}
trait HkdfHasher: OutputSizeUser {
const ZEROED: &'static [u8];
fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength>;
fn extract(salt: &[u8], ikm: &[u8]) -> Vec<u8>;
}
macro_rules! impl_hkdf_hasher {
() => {
const ZEROED: &'static [u8] =
&[0; <<Self as OutputSizeUser>::OutputSize as Unsigned>::USIZE];
fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength> {
hkdf::Hkdf::<Self>::new(None, ikm).expand(&label, okm)
}
let mut hkdf_label = Vec::new();
HkdfLabel {
length,
label: {
let mut v = b"tls13 ".to_vec();
v.extend_from_slice(label);
v.into()
},
context: context.to_vec().into(),
fn extract(salt: &[u8], ikm: &[u8]) -> Vec<u8> {
hkdf::Hkdf::<Self>::extract(Some(&[]), &[])
.0
.as_slice()
.to_vec()
}
.write(&mut hkdf_label)
.unwrap();
};
}
impl HkdfHasher for sha2::Sha256 {
impl_hkdf_hasher!();
}
impl HkdfHasher for sha2::Sha384 {
impl_hkdf_hasher!();
}
// TODO: use correct algo, the cipher suite hash algorithm!
let mut okm = [0u8; 42];
hkdf::Hkdf::<sha2::Sha256>::new(None, secret).expand(&hkdf_label, &mut okm)
// 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
fn hkdf_expand_label<H: HkdfHasher>(secret: &[u8], label: &[u8], context: &[u8]) -> Vec<u8> {
proto_struct! {
#[derive(Debug)]
pub struct HkdfLabel {
pub length: u16,
pub label: proto::ser_de::List<u8, u8>,
pub context: proto::ser_de::List<u8, u8>,
}
}
let mut hkdf_label = Vec::new();
HkdfLabel {
// Hash.length is its output length in bytes
length: H::output_size().try_into().unwrap(),
label: {
let mut v = b"tls13 ".to_vec();
v.extend_from_slice(label);
v.into()
},
context: context.to_vec().into(),
}
.write(&mut hkdf_label)
.unwrap();
let mut okm = [0u8; 128];
H::expand(secret, &hkdf_label, &mut okm).unwrap();
okm[..H::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<H: HkdfHasher>(secret: &[u8], label: &[u8], messages: ()) -> Vec<u8> {
hkdf_expand_label::<H>(secret, label, &[])
}
pub struct CryptoProvider {
zeroed_of_hash_size: &'static [u8],
hkdf_extract: fn(salt: &[u8], ikm: &[u8]) -> Vec<u8>,
derive_secret: fn(secret: &[u8], label: &[u8], messages: ()) -> Vec<u8>,
}
impl CryptoProvider {
fn new<H: HkdfHasher>() -> Self {
CryptoProvider {
zeroed_of_hash_size: H::ZEROED,
hkdf_extract: H::extract,
derive_secret: derive_secret::<H>,
}
}
}
/**
```text
0
|
v
PSK -> HKDF-Extract = Early Secret
|
+-----> Derive-Secret(., "ext binder" | "res binder", "")
| = binder_key
|
+-----> Derive-Secret(., "c e traffic", ClientHello)
| = client_early_traffic_secret
|
+-----> Derive-Secret(., "e exp master", ClientHello)
| = early_exporter_master_secret
v
Derive-Secret(., "derived", "")
|
v
(EC)DHE -> HKDF-Extract = Handshake Secret
|
+-----> Derive-Secret(., "c hs traffic",
| ClientHello...ServerHello)
| = client_handshake_traffic_secret
|
+-----> Derive-Secret(., "s hs traffic",
| ClientHello...ServerHello)
| = server_handshake_traffic_secret
v
Derive-Secret(., "derived", "")
|
v
0 -> HKDF-Extract = Master Secret
|
+-----> Derive-Secret(., "c ap traffic",
| ClientHello...server Finished)
| = client_application_traffic_secret_0
|
+-----> Derive-Secret(., "s ap traffic",
| ClientHello...server Finished)
| = server_application_traffic_secret_0
|
+-----> Derive-Secret(., "exp master",
| ClientHello...server Finished)
| = exporter_master_secret
|
+-----> Derive-Secret(., "res master",
ClientHello...client Finished)
= 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::<sha2::Sha256>(),
proto::CipherSuite::TLS_AES_256_GCM_SHA384 => CryptoProvider::new::<sha2::Sha384>(),
proto::CipherSuite::TLS_CHACHA20_POLY1305_SHA256 => CryptoProvider::new::<sha2::Sha256>(),
proto::CipherSuite::TLS_AES_128_CCM_SHA256 => CryptoProvider::new::<sha2::Sha256>(),
proto::CipherSuite::TLS_AES_128_CCM_8_SHA256 => CryptoProvider::new::<sha2::Sha256>(),
};
let derive_secret = |secret: &[u8], label: &[u8], messages: ()| {
hkdf_expand_label(secret, label, &[], 16) // todo: fix length
};
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", (/*empty*/)),
shared_secret.as_bytes(),
);
let client_handshake_traffic_secret = (provider.derive_secret)(
&handhske_secret,
b"c hs traffic",
(/*clienthello..serverhello*/),
);
let server_handshake_traffic_secret = (provider.derive_secret)(
&handhske_secret,
b"s hs traffic",
(/*clienthello..serverhello*/),
);
let master_secret = (provider.hkdf_extract)(
&(provider.derive_secret)(&handhske_secret, b"derived", (/*empty*/)),
&provider.zeroed_of_hash_size,
);
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*/),
);
dbg!("keys");
}
pub struct AeadKey {
@ -97,8 +253,6 @@ mod seq {
}
}
}
pub use seq::{SeqId, SeqIdGen};
use x25519_dalek::SharedSecret;
fn proto_algo_to_ring(algo: proto::CipherSuite) -> &'static aead::Algorithm {
match algo {

View file

@ -1,3 +1,5 @@
#![allow(unused)]
mod crypt;
pub mod proto;
@ -7,7 +9,6 @@ use std::{
io::{self, Read, Write},
};
use crypt::{SeqId, SeqIdGen};
use proto::CipherSuite;
use crate::proto::TLSPlaintext;
@ -61,7 +62,6 @@ mod stream_state {
pub fn write_record(&mut self, plaintext: TLSPlaintext) -> Result<SeqId> {
plaintext.write(&mut self.stream)?;
self.stream.flush()?;
Ok(self.write_seq_id.next())
}
@ -305,7 +305,7 @@ impl<W: Read + Write> ClientSetupConnection<W> {
dh_shared_secret.as_bytes()
);
crypt::compute_keys(dh_shared_secret);
crypt::compute_keys(dh_shared_secret, cipher_suite);
ConnectState::WaitEncryptedExtensions
}
@ -396,3 +396,10 @@ impl<R: Read> io::Read for LoggingWriter<R> {
len
}
}
pub trait LoggingWriterExt: Sized {
fn log(self) -> LoggingWriter<Self> {
LoggingWriter(self)
}
}
impl<W: io::Write> LoggingWriterExt for W {}

View file

@ -2,6 +2,9 @@ use std::net::TcpStream;
// An example program that makes a shitty HTTP/1.1 request.
fn main() {
let conn = tls::LoggingWriter(TcpStream::connect(("vps1.nilstrieb.dev", 443)).unwrap());
let conn = TcpStream::connect(("vps1.nilstrieb.dev", 443))
.unwrap()
//.log()
;
tls::ClientConnection::establish(conn, "vps1.nilstrieb.dev").unwrap();
}

View file

@ -106,7 +106,7 @@ macro_rules! proto_enum {
impl crate::proto::ser_de::Value for $name {
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.flush()?;
eprintln!("{}", stringify!($name));
//eprintln!("{}", stringify!($name));
mod discr_consts {
$(
#[allow(non_upper_case_globals)]
@ -116,7 +116,7 @@ macro_rules! proto_enum {
let write_len = |_w: &mut W, _len: usize| -> io::Result<()> {
_w.flush()?;
eprintln!("length");
//eprintln!("length");
$(
<$len_ty>::try_from(_len).unwrap().write(_w)?;
)?
@ -137,7 +137,7 @@ macro_rules! proto_enum {
$($(
w.flush()?;
eprintln!("{}", stringify!($field_name));
//eprintln!("{}", stringify!($field_name));
crate::proto::ser_de::Value::write($field_name, w)?;
)*)?