This commit is contained in:
nora 2024-07-20 13:53:30 +02:00
parent c767352e43
commit d4b0e9809f
2 changed files with 159 additions and 69 deletions

View file

@ -3,15 +3,19 @@
use crate::proto::{ use crate::proto::{
self, self,
ser_de::{proto_enum, proto_struct, Value}, ser_de::{proto_enum, proto_struct, Value},
Handshake,
}; };
use hkdf::{hmac::Hmac, HmacImpl}; use hkdf::{hmac::Hmac, HmacImpl};
use ring::aead::{self, Nonce}; use ring::aead::{self, Nonce};
pub use seq::{SeqId, SeqIdGen}; pub use seq::{SeqId, SeqIdGen};
use sha2::digest::{ use sha2::{
core_api::{self, CoreProxy}, digest::{
generic_array::ArrayLength, core_api::{self, CoreProxy},
typenum::Unsigned, generic_array::ArrayLength,
OutputSizeUser, typenum::Unsigned,
OutputSizeUser,
},
Digest,
}; };
use x25519_dalek::SharedSecret; use x25519_dalek::SharedSecret;
@ -27,7 +31,7 @@ impl From<Vec<u8>> for TlsCiphertext {
} }
} }
trait HkdfHasher: OutputSizeUser { trait TlsHasher: OutputSizeUser {
const ZEROED: &'static [u8]; const ZEROED: &'static [u8];
fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength>; fn expand(ikm: &[u8], label: &[u8], okm: &mut [u8]) -> Result<(), hkdf::InvalidLength>;
fn extract(salt: &[u8], ikm: &[u8]) -> Vec<u8>; fn extract(salt: &[u8], ikm: &[u8]) -> Vec<u8>;
@ -47,10 +51,10 @@ macro_rules! impl_hkdf_hasher {
} }
}; };
} }
impl HkdfHasher for sha2::Sha256 { impl TlsHasher for sha2::Sha256 {
impl_hkdf_hasher!(); impl_hkdf_hasher!();
} }
impl HkdfHasher for sha2::Sha384 { impl TlsHasher for sha2::Sha384 {
impl_hkdf_hasher!(); impl_hkdf_hasher!();
} }
@ -58,7 +62,7 @@ impl HkdfHasher for sha2::Sha384 {
// https://datatracker.ietf.org/doc/html/rfc8446#section-7.1 // 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 // 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> { fn hkdf_expand_label<H: TlsHasher>(secret: &[u8], label: &[u8], context: &[u8]) -> Vec<u8> {
proto_struct! { proto_struct! {
#[derive(Debug)] #[derive(Debug)]
pub struct HkdfLabel { pub struct HkdfLabel {
@ -70,7 +74,7 @@ fn hkdf_expand_label<H: HkdfHasher>(secret: &[u8], label: &[u8], context: &[u8])
let mut hkdf_label = Vec::new(); let mut hkdf_label = Vec::new();
HkdfLabel { HkdfLabel {
// Hash.length is its output length in bytes // Hash.length is its output length in bytes
length: H::output_size().try_into().unwrap(), length: <H as OutputSizeUser>::output_size().try_into().unwrap(),
label: { label: {
let mut v = b"tls13 ".to_vec(); let mut v = b"tls13 ".to_vec();
v.extend_from_slice(label); v.extend_from_slice(label);
@ -83,23 +87,23 @@ fn hkdf_expand_label<H: HkdfHasher>(secret: &[u8], label: &[u8], context: &[u8])
let mut okm = [0u8; 128]; let mut okm = [0u8; 128];
H::expand(secret, &hkdf_label, &mut okm).unwrap(); H::expand(secret, &hkdf_label, &mut okm).unwrap();
okm[..H::output_size()].to_vec() okm[..<H as OutputSizeUser>::output_size()].to_vec()
} }
/// Messages is the concatenation of the indicated handshake messages, /// Messages is the concatenation of the indicated handshake messages,
/// including the handshake message type and length fields, but not /// including the handshake message type and length fields, but not
/// including record layer headers. /// including record layer headers.
fn derive_secret<H: HkdfHasher>(secret: &[u8], label: &[u8], messages: ()) -> Vec<u8> { fn derive_secret<H: TlsHasher>(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec<u8> {
hkdf_expand_label::<H>(secret, label, &[]) hkdf_expand_label::<H>(secret, label, messages_hash)
} }
pub struct CryptoProvider { pub struct CryptoProvider {
zeroed_of_hash_size: &'static [u8], zeroed_of_hash_size: &'static [u8],
hkdf_extract: fn(salt: &[u8], ikm: &[u8]) -> Vec<u8>, hkdf_extract: fn(salt: &[u8], ikm: &[u8]) -> Vec<u8>,
derive_secret: fn(secret: &[u8], label: &[u8], messages: ()) -> Vec<u8>, derive_secret: fn(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec<u8>,
} }
impl CryptoProvider { impl CryptoProvider {
fn new<H: HkdfHasher>() -> Self { fn new<H: TlsHasher>() -> Self {
CryptoProvider { CryptoProvider {
zeroed_of_hash_size: H::ZEROED, zeroed_of_hash_size: H::ZEROED,
hkdf_extract: H::extract, hkdf_extract: H::extract,
@ -108,6 +112,25 @@ impl CryptoProvider {
} }
} }
pub struct TranscriptHash {
state: sha2::Sha256,
}
impl TranscriptHash {
pub fn new() -> Self {
Self {
state: sha2::Sha256::new(),
}
}
pub fn handshake(&mut self, handshake: &Handshake) {
let mut buf = Vec::new();
handshake.write(&mut buf);
self.state.update(&buf);
}
pub fn get_current(&self) -> Vec<u8> {
self.state.clone().finalize().to_vec()
}
}
/** /**
```text ```text
0 0
@ -159,53 +182,79 @@ impl CryptoProvider {
= resumption_master_secret = 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 early_secret = pub struct KeysAfterServerHello {
(provider.hkdf_extract)(&provider.zeroed_of_hash_size, &provider.zeroed_of_hash_size); provider: CryptoProvider,
handhske_secret: Vec<u8>,
pub client_handshake_traffic_secret: Vec<u8>,
pub server_handshake_traffic_secret: Vec<u8>,
master_secret: Vec<u8>,
}
let handhske_secret = (provider.hkdf_extract)( impl KeysAfterServerHello {
&(provider.derive_secret)(&early_secret, b"derived", (/*empty*/)), pub fn compute(
shared_secret.as_bytes(), shared_secret: SharedSecret,
); algo: proto::CipherSuite,
transcript: &TranscriptHash,
) -> Self {
let provider = match algo {
proto::CipherSuite::TLS_AES_128_GCM_SHA256 => CryptoProvider::new::<sha2::Sha256>(),
proto::CipherSuite::TLS_AES_256_GCM_SHA384 => {
todo!("anyhting but SHA256")
// 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 client_handshake_traffic_secret = (provider.derive_secret)( let early_secret =
&handhske_secret, (provider.hkdf_extract)(&provider.zeroed_of_hash_size, &provider.zeroed_of_hash_size);
b"c hs traffic",
(/*clienthello..serverhello*/),
);
let server_handshake_traffic_secret = (provider.derive_secret)( let handhske_secret = (provider.hkdf_extract)(
&handhske_secret, &(provider.derive_secret)(&early_secret, b"derived", &transcript.get_current()),
b"s hs traffic", shared_secret.as_bytes(),
(/*clienthello..serverhello*/), );
);
let master_secret = (provider.hkdf_extract)( let client_handshake_traffic_secret =
&(provider.derive_secret)(&handhske_secret, b"derived", (/*empty*/)), (provider.derive_secret)(&handhske_secret, b"c hs traffic", &transcript.get_current());
&provider.zeroed_of_hash_size,
);
let client_application_traffic_secret_0 = (provider.derive_secret)( let server_handshake_traffic_secret =
&master_secret, (provider.derive_secret)(&handhske_secret, b"s hs traffic", &transcript.get_current());
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*/),
);
let master_secret = (provider.hkdf_extract)(
&(provider.derive_secret)(
&handhske_secret,
b"derived",
&sha2::Sha256::new().finalize(),
),
&provider.zeroed_of_hash_size,
);
dbg!("keys"); Self {
provider,
handhske_secret,
client_handshake_traffic_secret,
server_handshake_traffic_secret,
master_secret,
}
}
fn after_handshake(self, transcript: &TranscriptHash) {
let client_application_traffic_secret_0 = (self.provider.derive_secret)(
&self.master_secret,
b"c ap traffic",
&transcript.get_current(),
);
let server_application_traffic_secret_0 = (self.provider.derive_secret)(
&self.master_secret,
b"s ap traffic",
&transcript.get_current(),
);
todo!()
}
} }
pub struct AeadKey { pub struct AeadKey {
@ -264,13 +313,11 @@ fn proto_algo_to_ring(algo: proto::CipherSuite) -> &'static aead::Algorithm {
} }
} }
fn encrypt(key: AeadKey, message: &[u8], seq: u8) -> Vec<u8> { fn encrypt(key: AeadKey, message: &[u8], seq: u8, nonce: Nonce) -> Vec<u8> {
let total_len = message.len() + key.key.algorithm().tag_len(); let total_len = message.len() + key.key.algorithm().tag_len();
let mut ciphertext_payload = Vec::with_capacity(total_len); let mut ciphertext_payload = Vec::with_capacity(total_len);
ciphertext_payload.extend_from_slice(message); ciphertext_payload.extend_from_slice(message);
// FIXME: dont use zero obviously
let nonce = aead::Nonce::assume_unique_for_key([0; aead::NONCE_LEN]);
// FIXME: fill out the AAD properly // FIXME: fill out the AAD properly
let aad = aead::Aad::from([0; 5]); let aad = aead::Aad::from([0; 5]);
key.key key.key
@ -280,6 +327,12 @@ fn encrypt(key: AeadKey, message: &[u8], seq: u8) -> Vec<u8> {
ciphertext_payload ciphertext_payload
} }
fn decrypt(key: AeadKey, msg: &mut [u8], nonce: Nonce) {
// FIXME: fill out the AAD properly
let aad = aead::Aad::from([0; 5]);
key.key.open_in_place(nonce, aad, msg);
}
pub struct TlsInnerPlaintext { pub struct TlsInnerPlaintext {
/// The `TLSPlaintext.fragment`` value /// The `TLSPlaintext.fragment`` value
pub content: Vec<u8>, pub content: Vec<u8>,
@ -289,7 +342,18 @@ pub struct TlsInnerPlaintext {
} }
impl TlsCiphertext { impl TlsCiphertext {
pub fn decrypt(&self, key: AeadKey, nonce: Nonce) -> TlsInnerPlaintext { pub fn decrypt(mut self, secret: &[u8], nonce: Nonce) -> TlsInnerPlaintext {
todo!() let key = hkdf_expand_label::<sha2::Sha256>(secret, b"key", &[]);
let iv = hkdf_expand_label::<sha2::Sha256>(secret, b"iv", &[]);
let key = AeadKey::new(proto::CipherSuite::TLS_AES_128_GCM_SHA256, secret);
decrypt(key, &mut self.encrypted_record, nonce);
TlsInnerPlaintext {
content: self.encrypted_record,
content_type: 0,
padding_len: 0,
}
} }
} }

View file

@ -9,7 +9,8 @@ use std::{
io::{self, Read, Write}, io::{self, Read, Write},
}; };
use proto::CipherSuite; use crypt::{KeysAfterServerHello, TranscriptHash};
use proto::{ser_de::Value, CipherSuite};
use crate::proto::TLSPlaintext; use crate::proto::TLSPlaintext;
@ -122,8 +123,11 @@ enum ConnectState {
legacy_session_id: [u8; 32], legacy_session_id: [u8; 32],
secret: RefCell<Option<x25519_dalek::EphemeralSecret>>, secret: RefCell<Option<x25519_dalek::EphemeralSecret>>,
cipher_suites: Vec<CipherSuite>, cipher_suites: Vec<CipherSuite>,
transcript: RefCell<TranscriptHash>,
},
WaitEncryptedExtensions {
keys: RefCell<Option<KeysAfterServerHello>>,
}, },
WaitEncryptedExtensions,
WaitCertificateRequest, WaitCertificateRequest,
WaitCertificate, WaitCertificate,
WaitCertificateVerify, WaitCertificateVerify,
@ -193,18 +197,24 @@ impl<W: Read + Write> ClientSetupConnection<W> {
] ]
.into(), .into(),
}; };
let mut transcript = TranscriptHash::new();
transcript.handshake(&handshake);
let plaintext = proto::TLSPlaintext::Handshake { handshake }; let plaintext = proto::TLSPlaintext::Handshake { handshake };
self.stream.write_flush_record(plaintext)?; self.stream.write_flush_record(plaintext)?;
ConnectState::WaitServerHello { ConnectState::WaitServerHello {
legacy_session_id, legacy_session_id,
secret: RefCell::new(Some(secret)), secret: RefCell::new(Some(secret)),
cipher_suites, cipher_suites,
transcript: RefCell::new(transcript),
} }
} }
ConnectState::WaitServerHello { ConnectState::WaitServerHello {
legacy_session_id, legacy_session_id,
secret, secret,
cipher_suites, cipher_suites,
transcript,
} => { } => {
let (frame, seq_id) = self.stream.read_record()?; let (frame, seq_id) = self.stream.read_record()?;
if frame.should_drop() { if frame.should_drop() {
@ -212,7 +222,7 @@ impl<W: Read + Write> ClientSetupConnection<W> {
} }
let proto::TLSPlaintext::Handshake { let proto::TLSPlaintext::Handshake {
handshake: handshake:
proto::Handshake::ServerHello { handshake @ proto::Handshake::ServerHello {
legacy_version, legacy_version,
random, random,
legacy_session_id_echo, legacy_session_id_echo,
@ -220,7 +230,7 @@ impl<W: Read + Write> ClientSetupConnection<W> {
legacy_compression_method, legacy_compression_method,
extensions, extensions,
}, },
} = frame } = &frame
else { else {
return unexpected_message!("expected ServerHello, got {frame:?}"); return unexpected_message!("expected ServerHello, got {frame:?}");
}; };
@ -229,7 +239,7 @@ impl<W: Read + Write> ClientSetupConnection<W> {
return Err(ErrorKind::HelloRetryRequest.into()); return Err(ErrorKind::HelloRetryRequest.into());
} }
if legacy_version != proto::LEGACY_TLSV12 { if *legacy_version != proto::LEGACY_TLSV12 {
return unexpected_message!( return unexpected_message!(
"unexpected TLS version in legacy_version field: {legacy_version:x?}" "unexpected TLS version in legacy_version field: {legacy_version:x?}"
); );
@ -247,12 +257,14 @@ impl<W: Read + Write> ClientSetupConnection<W> {
); );
} }
if legacy_compression_method != 0 { if *legacy_compression_method != 0 {
return unexpected_message!( return unexpected_message!(
"legacy compression method MUST be zero: {legacy_compression_method}" "legacy compression method MUST be zero: {legacy_compression_method}"
); );
} }
transcript.borrow_mut().handshake(&handshake);
let mut supported_versions = false; let mut supported_versions = false;
let mut server_key = None; let mut server_key = None;
@ -305,11 +317,17 @@ impl<W: Read + Write> ClientSetupConnection<W> {
dh_shared_secret.as_bytes() dh_shared_secret.as_bytes()
); );
crypt::compute_keys(dh_shared_secret, cipher_suite); let keys = crypt::KeysAfterServerHello::compute(
dh_shared_secret,
*cipher_suite,
&transcript.borrow(),
);
ConnectState::WaitEncryptedExtensions ConnectState::WaitEncryptedExtensions {
keys: RefCell::new(Some(keys)),
}
} }
ConnectState::WaitEncryptedExtensions => { ConnectState::WaitEncryptedExtensions { keys } => {
let (frame, seq_id) = self.stream.read_record()?; let (frame, seq_id) = self.stream.read_record()?;
if frame.should_drop() { if frame.should_drop() {
continue; continue;
@ -317,7 +335,15 @@ impl<W: Read + Write> ClientSetupConnection<W> {
let proto::TLSPlaintext::ApplicationData { data } = frame else { let proto::TLSPlaintext::ApplicationData { data } = frame else {
return unexpected_message!("expected ApplicationData, got {frame:?}"); return unexpected_message!("expected ApplicationData, got {frame:?}");
}; };
//crypt::TlsCiphertext::from(data).decrypt(key, seq_id.to_nonce()); // Encrypted with server_handshake_traffic_secret
crypt::TlsCiphertext::from(data).decrypt(
&keys
.borrow()
.as_ref()
.unwrap()
.server_handshake_traffic_secret,
seq_id.to_nonce(),
);
todo!() todo!()
} }