Skip to content
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
21 changes: 0 additions & 21 deletions rustls/src/client/client_emulator.rs

This file was deleted.

125 changes: 117 additions & 8 deletions rustls/src/client/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(feature = "impit")]
use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
Expand All @@ -9,10 +11,10 @@ use super::ech::EchMode;
use super::handy::ClientSessionMemoryCache;
use super::handy::{FailResolveClientCert, NoClientSessionStorage};
use crate::builder::{ConfigBuilder, WantsVerifier};
#[cfg(feature = "impit")]
use crate::client::client_emulator::BrowserEmulator;
#[cfg(doc)]
use crate::crypto;
#[cfg(feature = "impit")]
use crate::crypto::emulation::TlsFingerprint;
use crate::crypto::kx::NamedGroup;
use crate::crypto::{
CipherSuite, Credentials, CryptoProvider, Identity, SelectedCredential, SignatureScheme,
Expand Down Expand Up @@ -60,13 +62,12 @@ use crate::{DistinguishedName, KeyLog, compress, verify};
/// [`RootCertStore`]: crate::RootCertStore
#[derive(Clone, Debug)]
pub struct ClientConfig {
/// Whether this client is using browser-emulated settings.
/// This is used by the retch_rust project to emulate browsers' JA4 fingerprints.
/// TLS fingerprint configuration for browser emulation.
/// This allows fine-grained control over TLS parameters to match specific browser fingerprints.
///
/// Note that this can be only set by the builder's `with_browser_emulation` method.
/// Setting this field directly won't work correctly and might cause inconsistencies in your JA4 fingerprints.
/// Note that this should be set via the builder's `with_tls_fingerprint` method.
#[cfg(feature = "impit")]
pub browser_emulation: Option<BrowserEmulator>,
pub tls_fingerprint: Option<TlsFingerprint>,

/// Which ALPN protocols we include in our client hello.
/// If empty, no ALPN extension is sent.
Expand Down Expand Up @@ -557,6 +558,24 @@ pub struct WantsClientCert {
}

impl ConfigBuilder<ClientConfig, WantsClientCert> {
/// Enable TLS fingerprinting with a custom fingerprint.
#[cfg(feature = "impit")]
pub fn with_tls_fingerprint(
self,
fingerprint: TlsFingerprint,
) -> ConfigBuilder<ClientConfig, WantsClientCertWithTlsFingerprint> {
ConfigBuilder {
state: WantsClientCertWithTlsFingerprint {
verifier: self.state.verifier,
client_ech_mode: self.state.client_ech_mode,
tls_fingerprint: fingerprint,
},
provider: self.provider,
time_provider: self.time_provider,
side: PhantomData,
}
}

/// Sets a single certificate chain and matching private key for use
/// in client authentication.
///
Expand Down Expand Up @@ -607,7 +626,7 @@ impl ConfigBuilder<ClientConfig, WantsClientCert> {
alpn_protocols: Vec::new(),
resumption: Resumption::default(),
#[cfg(feature = "impit")]
browser_emulation: None,
tls_fingerprint: None,
max_fragment_size: None,
client_auth_cert_resolver,
enable_sni: true,
Expand All @@ -625,6 +644,96 @@ impl ConfigBuilder<ClientConfig, WantsClientCert> {
}
}

/// A config builder state where the caller needs to supply whether and how to provide a client
/// certificate, with TLS fingerprint enabled.
///
/// For more information, see the [`ConfigBuilder`] documentation.
#[cfg(feature = "impit")]
#[derive(Clone)]
pub struct WantsClientCertWithTlsFingerprint {
verifier: Arc<dyn verify::ServerVerifier>,
client_ech_mode: Option<EchMode>,
tls_fingerprint: TlsFingerprint,
}

#[cfg(feature = "impit")]
impl ConfigBuilder<ClientConfig, WantsClientCertWithTlsFingerprint> {
/// Sets a single certificate chain and matching private key for use
/// in client authentication.
pub fn with_client_auth_cert(
self,
identity: Arc<Identity<'static>>,
key_der: PrivateKeyDer<'static>,
) -> Result<ClientConfig, Error> {
let credentials = Credentials::from_der(identity, key_der, &self.provider)?;
self.with_client_credential_resolver(Arc::new(SingleCredential::from(credentials)))
}

/// Do not support client auth.
pub fn with_no_client_auth(self) -> Result<ClientConfig, Error> {
self.with_client_credential_resolver(Arc::new(FailResolveClientCert {}))
}

/// Sets a custom [`ClientCredentialResolver`].
pub fn with_client_credential_resolver(
self,
client_auth_cert_resolver: Arc<dyn ClientCredentialResolver>,
) -> Result<ClientConfig, Error> {
use crate::crypto::emulation::FingerprintCertCompressionAlgorithm;

self.provider.consistency_check()?;

// Determine cert compression based on fingerprint
let (cert_compressors, cert_decompressors) = if let Some(ref compression) = self
.state
.tls_fingerprint
.cert_compression
{
let compressors: Vec<_> = compression
.iter()
.filter_map(|alg| match alg {
FingerprintCertCompressionAlgorithm::Brotli => {
Some(compress::BROTLI_COMPRESSOR)
}
_ => None, // Only Brotli is supported for now
})
.collect();
let decompressors: Vec<_> = compression
.iter()
.filter_map(|alg| match alg {
FingerprintCertCompressionAlgorithm::Brotli => {
Some(compress::BROTLI_DECOMPRESSOR)
}
_ => None,
})
.collect();
(compressors, decompressors)
} else {
(vec![], vec![])
};

Ok(ClientConfig {
tls_fingerprint: Some(self.state.tls_fingerprint),
provider: self.provider,
alpn_protocols: Vec::new(),
resumption: Resumption::default(),
max_fragment_size: None,
client_auth_cert_resolver,
enable_sni: true,
verifier: self.state.verifier,
key_log: Arc::new(crate::KeyLogFile::new()),
enable_secret_extraction: false,
enable_early_data: false,
require_ems: cfg!(feature = "fips"),
time_provider: self.time_provider,
cert_compressors,
cert_compression_cache: Arc::new(compress::CompressionCache::default()),
cert_decompressors,
ech_mode: self.state.client_ech_mode,
})
}
}

/// Container for unsafe APIs
pub(super) mod danger {
use core::marker::PhantomData;
Expand Down
140 changes: 91 additions & 49 deletions rustls/src/client/hs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ use super::connection::ClientConnectionData;
use super::ech::{EchMode, EchState, EchStatus};
use super::{ClientHelloDetails, tls13};
use crate::check::inappropriate_handshake_message;
#[cfg(feature = "impit")]
use crate::client::client_emulator::{BrowserEmulator, BrowserType};
use crate::common_state::{CommonState, HandshakeKind, KxState, State};
use crate::crypto::cipher::Payload;
use crate::crypto::kx::{KeyExchangeAlgorithm, NamedGroup, StartedKeyExchange};
Expand Down Expand Up @@ -583,14 +581,16 @@ fn emit_client_hello_for_retry(
.collect();

#[cfg(feature = "impit")]
if let Some(BrowserEmulator {
browser_type: BrowserType::Chrome,
version: _,
}) = config.browser_emulation
{
offered_groups.push(NamedGroup::GREASE);
// offered_groups.push(NamedGroup::X25519Kyber768Draft00);
}
let offered_groups: Vec<NamedGroup> = if let Some(ref fingerprint) = config.tls_fingerprint {
// Use key exchange groups from TLS fingerprint
fingerprint
.key_exchange_groups
.iter()
.map(|g| g.to_named_group())
.collect()
} else {
offered_groups
};

let mut exts = Box::new(ClientExtensions {
supported_versions: Some(supported_versions),
Expand All @@ -602,43 +602,52 @@ fn emit_client_hello_for_retry(
.supported_verify_schemes(),
),
extended_master_secret_request: Some(()),
certificate_status_request: match config.verifier.request_ocsp_response() {
true => Some(CertificateStatusRequest::build_ocsp()),
false => None,
},
certificate_status_request: Some(CertificateStatusRequest::build_ocsp()),
protocols: extra_exts.protocols.clone(),
..Default::default()
});

#[cfg(feature = "impit")]
match config.browser_emulation {
Some(BrowserEmulator {
browser_type: BrowserType::Chrome,
version: _,
}) => {
if let Some(ref fingerprint) = config.tls_fingerprint {
// Apply TLS fingerprint extensions configuration
let ext_config = &fingerprint.extensions;

if ext_config.grease {
exts.reserved_grease = Some(());
}

if ext_config.signed_certificate_timestamp {
exts.signed_certificate_timestamp = Some(());
}

if ext_config.application_settings {
// hack - to avoid `Unexpected Message` when communicating with BoringSSL-based servers,
// we cannot send an actual ALPN protocol name list
let application_settings: PayloadU16 =
PayloadU16::new(vec![0x05, 0x69, 0x6d, 0x70, 0x69, 0x74]);

exts.reserved_grease = Some(());
exts.signed_certificate_timestamp = Some(());
exts.application_settings = Some(application_settings);
exts.renegotiation_info = Some(PayloadU8::empty());
if ext_config.use_new_alps_codepoint {
// Use new ALPS codepoint (17613 / 0x44cd) for Chrome 136+
exts.application_settings_new = Some(application_settings);
} else {
// Use old ALPS codepoint (17513 / 0x4469)
exts.application_settings = Some(application_settings);
}
}
Some(BrowserEmulator {
browser_type: BrowserType::Firefox,
version: _,
}) => {

if ext_config.delegated_credentials {
// TODO: We don't really support the delegated credentials extension yet, just sending it in the client hello message
let delegated_credentials_signature_algos =
PayloadU16::new(vec![0x04, 0x03, 0x05, 0x03, 0x06, 0x03, 0x02, 0x03]);

exts.delegated_credentials = Some(delegated_credentials_signature_algos);
}

if ext_config.renegotiation_info {
exts.renegotiation_info = Some(PayloadU8::empty());
exts.record_size_limit = Some(16385);
}
_ => {}

if let Some(record_size_limit) = ext_config.record_size_limit {
exts.record_size_limit = Some(record_size_limit);
}
}

if let Some(TransportParameters::Quic(v)) = &extra_exts.transport_parameters {
Expand Down Expand Up @@ -766,6 +775,28 @@ fn emit_client_hello_for_retry(
// but they also need to keep the same order as the previous ClientHello
exts.order_seed = input.hello.extension_order_seed;

#[cfg(feature = "impit")]
if let Some(ref fingerprint) = config.tls_fingerprint {
if !fingerprint
.extensions
.extension_order
.is_empty()
{
exts.contiguous_extensions = fingerprint
.extensions
.extension_order
.clone();
}

if !fingerprint
.extensions
.supported_versions
{
exts.supported_versions = None;
}
}

#[cfg(not(feature = "impit"))]
let mut cipher_suites: Vec<_> = config
.provider
.iter_cipher_suites()
Expand All @@ -775,31 +806,42 @@ fn emit_client_hello_for_retry(
})
.collect();

#[cfg(feature = "impit")]
let mut cipher_suites: Vec<_> = if let Some(ref fingerprint) = config.tls_fingerprint {
// Use cipher suites from TLS fingerprint with correct codes for advertising
fingerprint
.cipher_suites
.iter()
.map(|cs| cs.to_cipher_suite())
.collect()
} else {
config
.provider
.iter_cipher_suites()
.filter_map(|cs| match cs.usable_for_protocol(cx.common.protocol) {
true => Some(cs.suite()),
false => None,
})
.collect()
};

#[cfg(not(feature = "impit"))]
// We don't do renegotiation at all, in fact.
if supported_versions.tls12 {
// We don't do renegotiation at all, in fact.
cipher_suites.push(CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
}

#[cfg(feature = "impit")]
match config.browser_emulation {
// Chrome doesn't send this cipher suite.
Some(BrowserEmulator {
browser_type: BrowserType::Chrome,
version: _,
}) => {}
// Firefox also doesn't seem to send this cipher suite?
Some(BrowserEmulator {
browser_type: BrowserType::Firefox,
version: _,
}) => {}
_ => {
// We don't do renegotiation at all, in fact.
if supported_versions.tls12 {
// We don't do renegotiation at all, in fact.
cipher_suites.push(CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
}
// Skip adding TLS_EMPTY_RENEGOTIATION_INFO_SCSV when using tls_fingerprint
if config.tls_fingerprint.is_none() && supported_versions.tls12 {
cipher_suites.push(CipherSuite::TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
}

// Add padding extension if configured (RFC7685)
#[cfg(feature = "impit")]
if let Some(ref fingerprint) = config.tls_fingerprint {
if fingerprint.extensions.padding {
exts.padding = Some(Payload::Borrowed(&[]));
}
}

Expand Down
Loading
Loading