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, self,
ser_de::{proto_enum, proto_struct, Value}, ser_de::{proto_enum, proto_struct, Value},
}; };
use hkdf::{hmac::Hmac, HmacImpl};
use ring::aead::{self, Nonce}; 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 { pub struct TlsCiphertext {
/// The encrypted [`TlsInnerPlaintext`] record. /// The encrypted [`TlsInnerPlaintext`] record.
@ -19,37 +27,185 @@ impl From<Vec<u8>> for TlsCiphertext {
} }
} }
pub fn compute_keys(shared_secret: SharedSecret) { trait HkdfHasher: OutputSizeUser {
let hkdf_expand_label = |secret: &[u8], label: &[u8], context: &[u8], length| { const ZEROED: &'static [u8];
proto_struct! { fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength>;
#[derive(Debug)] fn extract(salt: &[u8], ikm: &[u8]) -> Vec<u8>;
pub struct HkdfLabel { }
pub length: u16, macro_rules! impl_hkdf_hasher {
pub label: proto::ser_de::List<u8,u8>, () => {
pub context: proto::ser_de::List<u8,u8>, 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(); fn extract(salt: &[u8], ikm: &[u8]) -> Vec<u8> {
HkdfLabel { hkdf::Hkdf::<Self>::extract(Some(&[]), &[])
length, .0
label: { .as_slice()
let mut v = b"tls13 ".to_vec(); .to_vec()
v.extend_from_slice(label);
v.into()
},
context: context.to_vec().into(),
} }
.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! // Key Schedule
let mut okm = [0u8; 42]; // https://datatracker.ietf.org/doc/html/rfc8446#section-7.1
hkdf::Hkdf::<sha2::Sha256>::new(None, secret).expand(&hkdf_label, &mut okm)
// 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: ()| { let early_secret =
hkdf_expand_label(secret, label, &[], 16) // todo: fix length (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 { 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 { fn proto_algo_to_ring(algo: proto::CipherSuite) -> &'static aead::Algorithm {
match algo { match algo {

View file

@ -1,3 +1,5 @@
#![allow(unused)]
mod crypt; mod crypt;
pub mod proto; pub mod proto;
@ -7,7 +9,6 @@ use std::{
io::{self, Read, Write}, io::{self, Read, Write},
}; };
use crypt::{SeqId, SeqIdGen};
use proto::CipherSuite; use proto::CipherSuite;
use crate::proto::TLSPlaintext; use crate::proto::TLSPlaintext;
@ -61,7 +62,6 @@ mod stream_state {
pub fn write_record(&mut self, plaintext: TLSPlaintext) -> Result<SeqId> { pub fn write_record(&mut self, plaintext: TLSPlaintext) -> Result<SeqId> {
plaintext.write(&mut self.stream)?; plaintext.write(&mut self.stream)?;
self.stream.flush()?;
Ok(self.write_seq_id.next()) Ok(self.write_seq_id.next())
} }
@ -305,7 +305,7 @@ impl<W: Read + Write> ClientSetupConnection<W> {
dh_shared_secret.as_bytes() dh_shared_secret.as_bytes()
); );
crypt::compute_keys(dh_shared_secret); crypt::compute_keys(dh_shared_secret, cipher_suite);
ConnectState::WaitEncryptedExtensions ConnectState::WaitEncryptedExtensions
} }
@ -396,3 +396,10 @@ impl<R: Read> io::Read for LoggingWriter<R> {
len 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. // An example program that makes a shitty HTTP/1.1 request.
fn main() { 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(); 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 { impl crate::proto::ser_de::Value for $name {
fn write<W: Write>(&self, w: &mut W) -> io::Result<()> { fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
w.flush()?; w.flush()?;
eprintln!("{}", stringify!($name)); //eprintln!("{}", stringify!($name));
mod discr_consts { mod discr_consts {
$( $(
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
@ -116,7 +116,7 @@ macro_rules! proto_enum {
let write_len = |_w: &mut W, _len: usize| -> io::Result<()> { let write_len = |_w: &mut W, _len: usize| -> io::Result<()> {
_w.flush()?; _w.flush()?;
eprintln!("length"); //eprintln!("length");
$( $(
<$len_ty>::try_from(_len).unwrap().write(_w)?; <$len_ty>::try_from(_len).unwrap().write(_w)?;
)? )?
@ -137,7 +137,7 @@ macro_rules! proto_enum {
$($( $($(
w.flush()?; w.flush()?;
eprintln!("{}", stringify!($field_name)); //eprintln!("{}", stringify!($field_name));
crate::proto::ser_de::Value::write($field_name, w)?; crate::proto::ser_de::Value::write($field_name, w)?;
)*)? )*)?