Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Sock puppets for CHs #2360

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 55 additions & 8 deletions neqo-transport/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ mod dump;
mod idle;
pub mod params;
mod saved;
mod sock_puppet;
mod state;
#[cfg(test)]
pub mod test_internal;
Expand Down Expand Up @@ -278,6 +279,7 @@ pub struct Connection {
release_resumption_token_timer: Option<Instant>,
conn_params: ConnectionParameters,
hrtime: hrtime::Handle,
protocols: Vec<String>,

/// For testing purposes it is sometimes necessary to inject frames that wouldn't
/// otherwise be sent, just to see how a connection handles them. Inserting them
Expand Down Expand Up @@ -381,10 +383,15 @@ impl Connection {
);

let tphandler = Rc::new(RefCell::new(tps));
let protocols = protocols
.iter()
.map(P::as_ref)
.map(String::from)
.collect::<Vec<_>>();
let crypto = Crypto::new(
conn_params.get_versions().initial(),
agent,
protocols.iter().map(P::as_ref).map(String::from).collect(),
protocols.clone(),
Rc::clone(&tphandler),
)?;

Expand Down Expand Up @@ -426,6 +433,7 @@ impl Connection {
conn_params,
hrtime: hrtime::Time::get(Self::LOOSE_TIMER_RESOLUTION),
quic_datagrams,
protocols,
#[cfg(test)]
test_frame_writer: None,
};
Expand Down Expand Up @@ -2160,6 +2168,7 @@ impl Connection {
let frame_stats = &mut stats.frame_tx;
self.crypto.write_frame(
PacketNumberSpace::ApplicationData,
self.conn_params.sni_slicing_enabled(),
builder,
tokens,
frame_stats,
Expand Down Expand Up @@ -2294,7 +2303,13 @@ impl Connection {
self.write_appdata_frames(builder, &mut tokens);
} else {
let stats = &mut self.stats.borrow_mut().frame_tx;
self.crypto.write_frame(space, builder, &mut tokens, stats);
self.crypto.write_frame(
space,
self.conn_params.sni_slicing_enabled(),
builder,
&mut tokens,
stats,
);
}
}

Expand Down Expand Up @@ -2366,7 +2381,7 @@ impl Connection {
let mut needs_padding = false;
let grease_quic_bit = self.can_grease_quic_bit();
let version = self.version();

let mut sock_puppet = None;
// Determine how we are sending packets (PTO, etc..).
let profile = self.loss_recovery.send_profile(&path.borrow(), now);
qdebug!("[{self}] output_path send_profile {profile:?}");
Expand All @@ -2380,6 +2395,18 @@ impl Connection {
continue;
};

sock_puppet =
(cspace == CryptoSpace::Initial && self.role == Role::Client).then(|| {
qdebug!("Creating sock puppet");
sock_puppet::sock_puppet(
tx,
path,
&self.loss_recovery,
&self.tps.borrow().local,
&self.protocols,
)
});

let header_start = encoder.len();
let (pt, mut builder) = Self::build_packet_header(
&path.borrow(),
Expand All @@ -2402,10 +2429,17 @@ impl Connection {
}

// Configure the limits and padding for this packet.
let aead_expansion = tx.expansion();
let mut reserved = tx.expansion();
if pn > 1 {
sock_puppet = None;
} else if let Some(sock_puppet) = &sock_puppet {
qdebug!("Reserving {} space for sock puppet CH", sock_puppet.len());
reserved += sock_puppet.len();
}

needs_padding |= builder.set_initial_limit(
&profile,
aead_expansion,
reserved,
self.paths
.primary()
.ok_or(Error::InternalError)?
Expand Down Expand Up @@ -2441,13 +2475,13 @@ impl Connection {
pn,
&builder.as_ref()[payload_start..],
path.borrow().tos(),
builder.len() + aead_expansion,
builder.len() + reserved,
);
qlog::packet_sent(
&self.qlog,
pt,
pn,
builder.len() - header_start + aead_expansion,
builder.len() - header_start + reserved,
&builder.as_ref()[payload_start..],
now,
);
Expand Down Expand Up @@ -2511,7 +2545,20 @@ impl Connection {
packets.len(),
profile.limit()
);
initial.track_padding(profile.limit() - packets.len());
let padding_len = profile.limit() - packets.len();
initial.track_padding(padding_len);
if let Some(sock_puppet) = sock_puppet {
if sock_puppet.len() <= padding_len {
qdebug!("Prepending sock puppet CH, len={}", sock_puppet.len());
packets.splice(0..0, sock_puppet);
} else {
qwarn!(
"Sock puppet CH too long ({} > {}), not prepending",
sock_puppet.len(),
padding_len
);
}
}
// These zeros aren't padding frames, they are an invalid all-zero coalesced
// packet, which is why we don't increase `frame_tx.padding` count here.
packets.resize(profile.limit(), 0);
Expand Down
28 changes: 28 additions & 0 deletions neqo-transport/src/connection/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ pub struct ConnectionParameters {
pacing: bool,
/// Whether the connection performs PLPMTUD.
pmtud: bool,
/// Whether the connection should use sock puppet CHs.
sock_puppet: bool,
/// Whether the connection should use SNI slicing.
sni_slicing: bool,
}

impl Default for ConnectionParameters {
Expand All @@ -107,6 +111,8 @@ impl Default for ConnectionParameters {
disable_migration: false,
pacing: true,
pmtud: false,
sock_puppet: true,
sni_slicing: false, // FIXME: This should be true by default.
}
}
}
Expand Down Expand Up @@ -367,6 +373,28 @@ impl ConnectionParameters {
self
}

#[must_use]
pub const fn sock_puppet_enabled(&self) -> bool {
self.sock_puppet
}

#[must_use]
pub const fn sock_puppet(mut self, sock_puppet: bool) -> Self {
self.sock_puppet = sock_puppet;
self
}

#[must_use]
pub const fn sni_slicing_enabled(&self) -> bool {
self.sni_slicing
}

#[must_use]
pub const fn sni_slicing(mut self, sni_slicing: bool) -> Self {
self.sni_slicing = sni_slicing;
self
}

/// # Errors
/// When a connection ID cannot be obtained.
/// # Panics
Expand Down
186 changes: 186 additions & 0 deletions neqo-transport/src/connection/sock_puppet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use neqo_common::Encoder;
use neqo_crypto::random;

use super::AddressValidationInfo;
use crate::{
crypto::{CryptoDxState, CryptoSpace, CryptoStreams},
path::PathRef,
recovery::LossRecovery,
stats::FrameStats,
tparams::TransportParameters,
tracking::PacketNumberSpace,
Connection, Version,
};

/// See <https://tls13.xargs.org/#client-hello/annotated>
fn generate_ch(sni: &[u8], protocols: &[String], tps: &TransportParameters) -> Vec<u8> {
// Extensions
let mut extensions = Encoder::new();

let mut list_entry = Encoder::new();
list_entry.encode_uint::<u8>(1, 0x00); // list entry is type 0x00 "DNS hostname"
list_entry.encode_vec(2, sni);
let mut server_name_extension = Encoder::new();
server_name_extension.encode_vec(2, list_entry.as_ref()); // "server name" extension data

extensions.encode_uint::<u8>(2, 0x00); // assigned value for extension "server name"
extensions.encode_vec(2, server_name_extension.as_ref()); // server name extension data

let mut supported_groups = Encoder::new();
supported_groups.encode_vec(
2,
&[
0x00, 0x1d, // assigned value for the curve "x25519"
0x00, 0x17, // assigned value for the curve "secp256r1"
0x00, 0x18, // assigned value for the curve "secp384r1"
0x00, 0x19, // assigned value for the curve "secp521r1"
],
);

extensions.encode_uint::<u8>(2, 0x0a); // assigned value for extension "supported groups"
extensions.encode_vec(2, supported_groups.as_ref()); // supported groups extension data

extensions.encode_uint::<u8>(2, 0x17); // assigned value for extension "Extended Master Secret"
extensions.encode_vec(2, &[]); // 0 bytes of "Extended Master Secret" extension data

let mut signature_algorithms = Encoder::new();
signature_algorithms.encode_vec(
2,
&[
0x04, 0x03, // assigned value for ECDSA-SECP256r1-SHA256
0x05, 0x03, // assigned value for ECDSA-SECP384r1-SHA384
0x06, 0x03, // assigned value for ECDSA-SECP521r1-SHA512
0x02, 0x03, // assigned value for ECDSA-SHA1
0x08, 0x04, // assigned value for RSA-PSS-RSAE-SHA256
0x08, 0x05, // assigned value for RSA-PSS-RSAE-SHA384
0x08, 0x06, // assigned value for RSA-PSS-RSAE-SHA512
0x08, 0x09, // assigned value for RSA-PSS-PSS-SHA256
0x04, 0x01, // assigned value for RSA-PKCS1-SHA256
0x05, 0x01, // assigned value for RSA-PKCS1-SHA384
0x06, 0x01, // assigned value for RSA-PKCS1-SHA512
0x02, 0x01, // assigned value for RSA-PKCS1-SHA1
],
);

extensions.encode_uint::<u8>(2, 0x0d); // assigned value for extension "Signature Algorithms"
extensions.encode_vec(2, signature_algorithms.as_ref()); // "Signature Algorithms" extension data

let mut supported_versions = Encoder::new();
supported_versions.encode_vec(1, &[0x03, 0x04]); // assigned value for TLS 1.3

extensions.encode_uint::<u8>(2, 0x2b); // assigned value for extension "Supported Versions"
extensions.encode_vec(2, supported_versions.as_ref()); // "Supported Versions" extension data

let mut psk_key_exchange_modes = Encoder::new();
psk_key_exchange_modes.encode_vec(1, &[0x01]); // assigned value for "PSK with (EC)DHE key establishment"

extensions.encode_uint::<u8>(2, 0x2d); // assigned value for extension "PSK Key Exchange Modes"
extensions.encode_vec(2, psk_key_exchange_modes.as_ref()); // "PSK Key Exchange Modes" extension data

let mut key_share = Encoder::new();
key_share.encode_uint::<u8>(2, 0x1d); // assigned value for x25519 (key exchange via curve25519)
key_share.encode_vec(2, &random::<32>()); // 32 bytes of public key
key_share.encode_uint::<u8>(2, 0x17); // assigned value for secp256r1 (key exchange via NIST P-256)
key_share.encode_vec(2, &random::<65>()); // 65 bytes of public key

let mut key_share_list = Encoder::new();
key_share_list.encode_vec(2, key_share.as_ref()); // first (and only) key share entry

extensions.encode_uint::<u8>(2, 0x33); // assigned value for extension "Key Share"
extensions.encode_vec(2, key_share_list.as_ref()); // "Key Share" extension data

let mut transport_parameters = Encoder::default();
tps.encode(&mut transport_parameters);

extensions.encode_uint::<u8>(2, 0x39);
extensions.encode_vec(2, transport_parameters.as_ref());

let mut alpn = Encoder::new();
for protocol in protocols {
alpn.encode_vec(1, protocol.as_bytes());
}
let mut alpn_list = Encoder::new();
alpn_list.encode_vec(2, alpn.as_ref());

extensions.encode_uint::<u8>(2, 0x10);
extensions.encode_vec(2, alpn_list.as_ref());

extensions.encode_uint::<u8>(2, 0x1c); // record size limit
extensions.encode_vec(2, &[0x40, 0x01]);

extensions.encode_uint::<u16>(2, 0xff01); // renegotiation info
extensions.encode_vec(2, &[0x00]);

extensions.encode_uint::<u8>(2, 0x05); // status request
extensions.encode_vec(2, &[0x01, 0x00, 0x00, 0x00, 0x00]);

// Handshake Header
let mut handshake_data = Encoder::new();
handshake_data.encode(&[0x03, 0x03]); // Client Version
handshake_data.encode(&random::<32>()); // Client Random
handshake_data.encode_uint::<u8>(1, 0); // 0 bytes of session ID
handshake_data.encode_vec(
2,
&[
// Cipher Suites
0x13, 0x01, // assigned value for TLS_AES_128_GCM_SHA256
0x13, 0x03, // assigned value for TLS_CHACHA20_POLY1305_SHA256
0x13, 0x02, // assigned value for TLS_AES_256_GCM_SHA384
],
);
// Compression Methods
handshake_data.encode_vec(1, &[0x00]); // assigned value for "null" compression
handshake_data.encode_vec(2, extensions.as_ref()); // Extensions

let mut handshake_message = Encoder::new();
handshake_message.encode(&[0x01]); // handshake message type 0x01 (client hello)
handshake_message.encode_vec(3, handshake_data.as_ref()); // client hello data

handshake_message.as_ref().to_vec()
}

pub fn sock_puppet(
tx: &mut CryptoDxState,
path: &PathRef,
loss_recovery: &LossRecovery,
tps: &TransportParameters,
protocols: &[String],
) -> Vec<u8> {
let encoder = Encoder::new();
let (_pt, mut builder) = Connection::build_packet_header(
&path.borrow(),
CryptoSpace::Initial,
encoder,
tx,
&AddressValidationInfo::None,
Version::Version1,
false,
);
_ = Connection::add_packet_number(
&mut builder,
tx,
loss_recovery.largest_acknowledged_pn(PacketNumberSpace::Initial),
);
let mut cstreams = CryptoStreams::default();
cstreams
.send(
PacketNumberSpace::Initial,
&generate_ch(b"github.com", protocols, tps),
)
.unwrap();
cstreams.write_frame(
PacketNumberSpace::Initial,
false,
&mut builder,
&mut Vec::new(),
&mut FrameStats::default(),
);

builder.build(tx).unwrap().into()
}
Loading
Loading