fix several bugs

This commit is contained in:
nora 2024-08-03 14:59:12 +02:00
parent 0f93d225e7
commit c518c09937
6 changed files with 99 additions and 49 deletions

View file

@ -13,11 +13,32 @@ use super::{keys, TlsInnerPlaintext};
pub type AeadKey = aes_gcm::Key<Aes128Gcm>; pub type AeadKey = aes_gcm::Key<Aes128Gcm>;
pub type Nonce = aes_gcm::Nonce<<Aes128Gcm as aes_gcm::AeadCore>::NonceSize>; pub type Nonce = aes_gcm::Nonce<<Aes128Gcm as aes_gcm::AeadCore>::NonceSize>;
fn decrypt(key: &AeadKey, ciphertext: &[u8], nonce: &Nonce, additional_data: &[u8]) -> Vec<u8> { pub struct TlsNonce {
nonce: Nonce,
}
impl TlsNonce {
pub fn new(nonce: Nonce) -> Self {
Self { nonce }
}
fn get_aead_nonce(mut self, iv: &[u8]) -> Nonce {
// <https://datatracker.ietf.org/doc/html/rfc8446#section-5.3>
// 2. The padded sequence number is XORed with either the static
// client_write_iv or server_write_iv (depending on the role).
assert_eq!(self.nonce.len(), iv.len());
self.nonce
.as_mut_slice()
.iter_mut()
.zip(iv)
.for_each(|(lhs, rhs)| *lhs ^= rhs);
self.nonce
}
}
fn decrypt(key: &AeadKey, ciphertext: &[u8], nonce: Nonce, additional_data: &[u8]) -> Vec<u8> {
let cipher = Aes128Gcm::new(key); let cipher = Aes128Gcm::new(key);
cipher cipher
.decrypt( .decrypt(
nonce, &nonce,
Payload { Payload {
msg: ciphertext, msg: ciphertext,
aad: additional_data, aad: additional_data,
@ -29,19 +50,27 @@ fn decrypt(key: &AeadKey, ciphertext: &[u8], nonce: &Nonce, additional_data: &[u
pub fn decrypt_ciphertext( pub fn decrypt_ciphertext(
encrypted_record: &[u8], encrypted_record: &[u8],
secret: &[u8], secret: &[u8],
nonce: Nonce, nonce: TlsNonce,
) -> TlsInnerPlaintext { ) -> TlsInnerPlaintext {
// <https://datatracker.ietf.org/doc/html/rfc8446#section-7.3> // <https://datatracker.ietf.org/doc/html/rfc8446#section-7.3>
let key = keys::hkdf_expand_label::<sha2::Sha256>(secret, b"key", &[], 128/8); let key = keys::hkdf_expand_label(secret, b"key", &[], 128 / 8);
let key = aes_gcm::Key::<Aes128Gcm>::from_slice(&key[0..(128 / 8)]); let key = aes_gcm::Key::<Aes128Gcm>::from_slice(&key[0..(128 / 8)]);
let iv = keys::hkdf_expand_label(secret, b"iv", &[], 96 / 8);
// <https://datatracker.ietf.org/doc/html/rfc8446#section-5.2>
// TLS v1.2 0x03, 0x03 // TLS v1.2 0x03, 0x03
let mut additional_data = [proto::TLSPlaintext::APPLICATION_DATA, 0x03, 0x03, 0, 0]; let mut additional_data = [proto::TLSPlaintext::APPLICATION_DATA, 0x03, 0x03, 0, 0];
let ciphertext_len = encrypted_record.as_ref().len() as u16; let ciphertext_len = encrypted_record.as_ref().len() as u16;
additional_data[3..].copy_from_slice(&ciphertext_len.to_be_bytes()); additional_data[3..].copy_from_slice(&ciphertext_len.to_be_bytes());
let result = decrypt(key, encrypted_record, &nonce, &additional_data); // <https://datatracker.ietf.org/doc/html/rfc8446#section-5.3>
let result = decrypt(
key,
encrypted_record,
nonce.get_aead_nonce(&iv),
&additional_data,
);
TlsInnerPlaintext { TlsInnerPlaintext {
content: result, content: result,

View file

@ -1,15 +1,16 @@
use sha2::Digest; use hkdf::Hkdf;
use sha2::{Digest, Sha256};
use x25519_dalek::SharedSecret; use x25519_dalek::SharedSecret;
use crate::proto::{self, ser_de::Value}; use crate::proto::{self, ser_de::Value};
use super::{CryptoProvider, TlsHasher}; use super::CryptoProvider;
// Key Schedule // Key Schedule
// 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
pub(super) fn hkdf_expand_label<H: TlsHasher>( pub(super) fn hkdf_expand_label(
secret: &[u8], secret: &[u8],
label: &[u8], label: &[u8],
context: &[u8], context: &[u8],
@ -38,19 +39,18 @@ pub(super) fn hkdf_expand_label<H: TlsHasher>(
.unwrap(); .unwrap();
let mut okm = [0u8; 128]; let mut okm = [0u8; 128];
H::expand(secret, &hkdf_label, &mut okm).unwrap(); hkdf::Hkdf::<sha2::Sha256>::from_prk(secret)
.unwrap()
.expand(&hkdf_label, &mut okm[..length])
.unwrap();
okm[..length].to_vec() okm[..length].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.
pub(super) fn derive_secret<H: TlsHasher>( pub(super) fn derive_secret(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec<u8> {
secret: &[u8], hkdf_expand_label(secret, label, messages_hash, Sha256::output_size())
label: &[u8],
messages_hash: &[u8],
) -> Vec<u8> {
hkdf_expand_label::<H>(secret, label, messages_hash, H::output_size())
} }
pub struct TranscriptHash { pub struct TranscriptHash {
@ -150,26 +150,40 @@ impl KeysAfterServerHello {
proto::CipherSuite::TLS_AES_128_CCM_8_SHA256 => CryptoProvider::new::<sha2::Sha256>(), proto::CipherSuite::TLS_AES_128_CCM_8_SHA256 => CryptoProvider::new::<sha2::Sha256>(),
}; };
let early_secret = let (early_secret, _hkdf_use_this_its_nice) = hkdf::Hkdf::<Sha256>::extract(
(provider.hkdf_extract)(&provider.zeroed_of_hash_size, &provider.zeroed_of_hash_size); Some(provider.zeroed_of_hash_size),
provider.zeroed_of_hash_size,
let handhske_secret = (provider.hkdf_extract)(
&(provider.derive_secret)(&early_secret, b"derived", &transcript.get_current()),
shared_secret.as_bytes(),
); );
let client_handshake_traffic_secret = let early_secret_derived =
(provider.derive_secret)(&handhske_secret, b"c hs traffic", &transcript.get_current()); derive_secret(&early_secret, b"derived", &sha2::Sha256::new().finalize());
println!("early_secret {:?}", early_secret);
let server_handshake_traffic_secret = println!("early_secret_derived {:?}", early_secret_derived);
(provider.derive_secret)(&handhske_secret, b"s hs traffic", &transcript.get_current());
let master_secret = (provider.hkdf_extract)( let (handshake_secret, _) =
&(provider.derive_secret)( Hkdf::<Sha256>::extract(Some(&early_secret_derived), shared_secret.as_bytes());
&handhske_secret,
b"derived", let client_handshake_traffic_secret = derive_secret(
&sha2::Sha256::new().finalize(), &handshake_secret,
), b"c hs traffic",
&transcript.get_current(),
);
let server_handshake_traffic_secret = derive_secret(
&handshake_secret,
b"s hs traffic",
&transcript.get_current(),
);
let handshake_secret_derived = derive_secret(
&handshake_secret,
b"derived",
&sha2::Sha256::new().finalize(),
);
let (master_secret, _) = Hkdf::<Sha256>::extract(
Some(&handshake_secret_derived),
&provider.zeroed_of_hash_size, &provider.zeroed_of_hash_size,
); );
@ -177,18 +191,18 @@ impl KeysAfterServerHello {
provider, provider,
client_handshake_traffic_secret, client_handshake_traffic_secret,
server_handshake_traffic_secret, server_handshake_traffic_secret,
master_secret, master_secret: master_secret.to_vec(),
} }
} }
#[allow(dead_code)] #[allow(dead_code)]
fn after_handshake(self, transcript: &TranscriptHash) { fn after_handshake(self, transcript: &TranscriptHash) {
let _client_application_traffic_secret_0 = (self.provider.derive_secret)( let _client_application_traffic_secret_0 = derive_secret(
&self.master_secret, &self.master_secret,
b"c ap traffic", b"c ap traffic",
&transcript.get_current(), &transcript.get_current(),
); );
let _server_application_traffic_secret_0 = (self.provider.derive_secret)( let _server_application_traffic_secret_0 = derive_secret(
&self.master_secret, &self.master_secret,
b"s ap traffic", b"s ap traffic",
&transcript.get_current(), &transcript.get_current(),

View file

@ -36,14 +36,12 @@ impl TlsHasher for sha2::Sha384 {
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_hash: &[u8]) -> Vec<u8>,
} }
impl CryptoProvider { impl CryptoProvider {
fn new<H: TlsHasher>() -> 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,
derive_secret: keys::derive_secret::<H>,
} }
} }
} }
@ -51,7 +49,7 @@ impl CryptoProvider {
mod seq { mod seq {
use std::cell::Cell; use std::cell::Cell;
use super::aead::Nonce; use super::aead::{Nonce, TlsNonce};
/// The sequence ID generator. /// The sequence ID generator.
/// There is a separate one maintained for reading and writing. /// There is a separate one maintained for reading and writing.
@ -71,10 +69,15 @@ mod seq {
// Don't implement `Clone` to ensure every seq id is only used once. // Don't implement `Clone` to ensure every seq id is only used once.
pub struct SeqId(u64); pub struct SeqId(u64);
impl SeqId { impl SeqId {
pub fn to_nonce(self) -> Nonce { pub fn to_nonce(self) -> TlsNonce {
// <https://datatracker.ietf.org/doc/html/rfc8446#section-5.3>
// 1. The 64-bit record sequence number is encoded in network byte
// order and padded to the left with zeros to iv_length.
let mut nonce = [0; 12]; let mut nonce = [0; 12];
nonce[4..].copy_from_slice(&self.0.to_be_bytes()); nonce[4..12].copy_from_slice(&self.0.to_be_bytes());
Nonce::from(nonce) let nonce = Nonce::from(nonce);
TlsNonce::new(nonce)
} }
} }
} }

View file

@ -8,10 +8,7 @@ use std::{
}; };
use crypto::keys::{KeysAfterServerHello, TranscriptHash}; use crypto::keys::{KeysAfterServerHello, TranscriptHash};
use proto::{ use proto::CipherSuite;
ser_de::{FrameReader, Value},
CipherSuite, TlsCiphertext,
};
use crate::proto::TLSPlaintext; use crate::proto::TLSPlaintext;
@ -149,6 +146,7 @@ impl<W: Read + Write> ClientSetupConnection<W> {
let public = x25519_dalek::PublicKey::from(&secret); let public = x25519_dalek::PublicKey::from(&secret);
let legacy_session_id = rand::random::<[u8; 32]>(); let legacy_session_id = rand::random::<[u8; 32]>();
// Only AES-128 with SHA-256
let cipher_suites = vec![proto::CipherSuite::TLS_AES_128_GCM_SHA256]; let cipher_suites = vec![proto::CipherSuite::TLS_AES_128_GCM_SHA256];
let handshake = proto::Handshake::ClientHello { let handshake = proto::Handshake::ClientHello {
@ -335,7 +333,10 @@ impl<W: Read + Write> ClientSetupConnection<W> {
continue; continue;
} }
// Frame is a TLSCiphertext. // Frame is a TLSCiphertext.
let proto::TLSPlaintext::ApplicationData { data: encrypted_record } = frame else { let proto::TLSPlaintext::ApplicationData {
data: encrypted_record,
} = frame
else {
return unexpected_message!("expected ApplicationData, got {frame:?}"); return unexpected_message!("expected ApplicationData, got {frame:?}");
}; };
// Encrypted with server_handshake_traffic_secret // Encrypted with server_handshake_traffic_secret

View file

@ -2,9 +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 = TcpStream::connect(("vps1.nilstrieb.dev", 443)) let conn = TcpStream::connect(("noratrieb.dev", 443))
.unwrap() .unwrap()
//.log() //.log()
; ;
tls::ClientConnection::establish(conn, "vps1.nilstrieb.dev").unwrap(); tls::ClientConnection::establish(conn, "noratrieb.dev").unwrap();
} }

View file

@ -270,7 +270,10 @@ impl<T: Value, Len: Value + Into<usize> + TryFrom<usize> + Default> Value for Li
} }
impl<T: Value, Len: Value + Into<usize> + TryFrom<usize> + Default> List<T, Len> { impl<T: Value, Len: Value + Into<usize> + TryFrom<usize> + Default> List<T, Len> {
pub fn read_for_byte_length<R: Read>(mut remaining_byte_size: usize, r: &mut FrameReader<R>) -> crate::Result<Self> { pub fn read_for_byte_length<R: Read>(
mut remaining_byte_size: usize,
r: &mut FrameReader<R>,
) -> crate::Result<Self> {
let mut v = Vec::new(); let mut v = Vec::new();
while remaining_byte_size > 0 { while remaining_byte_size > 0 {
let value = T::read(r)?; let value = T::read(r)?;