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 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);
cipher
.decrypt(
nonce,
&nonce,
Payload {
msg: ciphertext,
aad: additional_data,
@ -29,19 +50,27 @@ fn decrypt(key: &AeadKey, ciphertext: &[u8], nonce: &Nonce, additional_data: &[u
pub fn decrypt_ciphertext(
encrypted_record: &[u8],
secret: &[u8],
nonce: Nonce,
nonce: TlsNonce,
) -> TlsInnerPlaintext {
// <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 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
let mut additional_data = [proto::TLSPlaintext::APPLICATION_DATA, 0x03, 0x03, 0, 0];
let ciphertext_len = encrypted_record.as_ref().len() as u16;
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 {
content: result,

View file

@ -1,15 +1,16 @@
use sha2::Digest;
use hkdf::Hkdf;
use sha2::{Digest, Sha256};
use x25519_dalek::SharedSecret;
use crate::proto::{self, ser_de::Value};
use super::{CryptoProvider, TlsHasher};
use super::CryptoProvider;
// 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
pub(super) fn hkdf_expand_label<H: TlsHasher>(
pub(super) fn hkdf_expand_label(
secret: &[u8],
label: &[u8],
context: &[u8],
@ -38,19 +39,18 @@ pub(super) fn hkdf_expand_label<H: TlsHasher>(
.unwrap();
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()
}
/// Messages is the concatenation of the indicated handshake messages,
/// including the handshake message type and length fields, but not
/// including record layer headers.
pub(super) fn derive_secret<H: TlsHasher>(
secret: &[u8],
label: &[u8],
messages_hash: &[u8],
) -> Vec<u8> {
hkdf_expand_label::<H>(secret, label, messages_hash, H::output_size())
pub(super) fn derive_secret(secret: &[u8], label: &[u8], messages_hash: &[u8]) -> Vec<u8> {
hkdf_expand_label(secret, label, messages_hash, Sha256::output_size())
}
pub struct TranscriptHash {
@ -150,26 +150,40 @@ impl KeysAfterServerHello {
proto::CipherSuite::TLS_AES_128_CCM_8_SHA256 => CryptoProvider::new::<sha2::Sha256>(),
};
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", &transcript.get_current()),
shared_secret.as_bytes(),
let (early_secret, _hkdf_use_this_its_nice) = hkdf::Hkdf::<Sha256>::extract(
Some(provider.zeroed_of_hash_size),
provider.zeroed_of_hash_size,
);
let client_handshake_traffic_secret =
(provider.derive_secret)(&handhske_secret, b"c hs traffic", &transcript.get_current());
let early_secret_derived =
derive_secret(&early_secret, b"derived", &sha2::Sha256::new().finalize());
println!("early_secret {:?}", early_secret);
let server_handshake_traffic_secret =
(provider.derive_secret)(&handhske_secret, b"s hs traffic", &transcript.get_current());
println!("early_secret_derived {:?}", early_secret_derived);
let master_secret = (provider.hkdf_extract)(
&(provider.derive_secret)(
&handhske_secret,
b"derived",
&sha2::Sha256::new().finalize(),
),
let (handshake_secret, _) =
Hkdf::<Sha256>::extract(Some(&early_secret_derived), shared_secret.as_bytes());
let client_handshake_traffic_secret = derive_secret(
&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,
);
@ -177,18 +191,18 @@ impl KeysAfterServerHello {
provider,
client_handshake_traffic_secret,
server_handshake_traffic_secret,
master_secret,
master_secret: master_secret.to_vec(),
}
}
#[allow(dead_code)]
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,
b"c ap traffic",
&transcript.get_current(),
);
let _server_application_traffic_secret_0 = (self.provider.derive_secret)(
let _server_application_traffic_secret_0 = derive_secret(
&self.master_secret,
b"s ap traffic",
&transcript.get_current(),

View file

@ -36,14 +36,12 @@ impl TlsHasher for sha2::Sha384 {
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_hash: &[u8]) -> Vec<u8>,
}
impl CryptoProvider {
fn new<H: TlsHasher>() -> Self {
CryptoProvider {
zeroed_of_hash_size: H::ZEROED,
hkdf_extract: H::extract,
derive_secret: keys::derive_secret::<H>,
}
}
}
@ -51,7 +49,7 @@ impl CryptoProvider {
mod seq {
use std::cell::Cell;
use super::aead::Nonce;
use super::aead::{Nonce, TlsNonce};
/// The sequence ID generator.
/// 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.
pub struct SeqId(u64);
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];
nonce[4..].copy_from_slice(&self.0.to_be_bytes());
Nonce::from(nonce)
nonce[4..12].copy_from_slice(&self.0.to_be_bytes());
let nonce = Nonce::from(nonce);
TlsNonce::new(nonce)
}
}
}

View file

@ -8,10 +8,7 @@ use std::{
};
use crypto::keys::{KeysAfterServerHello, TranscriptHash};
use proto::{
ser_de::{FrameReader, Value},
CipherSuite, TlsCiphertext,
};
use proto::CipherSuite;
use crate::proto::TLSPlaintext;
@ -149,6 +146,7 @@ impl<W: Read + Write> ClientSetupConnection<W> {
let public = x25519_dalek::PublicKey::from(&secret);
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 handshake = proto::Handshake::ClientHello {
@ -335,7 +333,10 @@ impl<W: Read + Write> ClientSetupConnection<W> {
continue;
}
// 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:?}");
};
// 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.
fn main() {
let conn = TcpStream::connect(("vps1.nilstrieb.dev", 443))
let conn = TcpStream::connect(("noratrieb.dev", 443))
.unwrap()
//.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> {
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();
while remaining_byte_size > 0 {
let value = T::read(r)?;