diff --git a/rustls-mio/Cargo.toml b/rustls-mio/Cargo.toml index 60edc729bfb..49e2d5c6059 100644 --- a/rustls-mio/Cargo.toml +++ b/rustls-mio/Cargo.toml @@ -22,6 +22,7 @@ log = { version = "0.4.4", optional = true } rustls = { path = "../rustls" } sct = "0.6" webpki = "0.21.0" +trust-dns-resolver = { version = "0.12.0", features = ["dns-over-rustls", "dns-over-https-rustls"] } [dev-dependencies] ct-logs = "0.6" @@ -47,6 +48,10 @@ path = "examples/tlsserver.rs" name = "simpleclient" path = "examples/simpleclient.rs" +[[example]] +name = "esniclient" +path = "examples/esniclient.rs" + [[example]] name = "simple_0rtt_client" path = "examples/simple_0rtt_client.rs" diff --git a/rustls-mio/examples/esniclient.rs b/rustls-mio/examples/esniclient.rs new file mode 100644 index 00000000000..86b1168e60e --- /dev/null +++ b/rustls-mio/examples/esniclient.rs @@ -0,0 +1,104 @@ +use std::sync::Arc; + +use std::net::TcpStream; +use std::io::{Read, Write, stdout}; +use std::iter::FromIterator; + +use rustls; +use webpki; +use webpki_roots; +use rustls::Session; +use base64::decode; + +extern crate trust_dns_resolver; +use trust_dns_resolver::config::*; +use trust_dns_resolver::Resolver; + +fn main() { + let domain = "canbe.esni.defo.ie"; + println!("\nContacting {:?} over ESNI\n", domain); + + let dns_config = ResolverConfig::cloudflare_https(); + let opts = ResolverOpts::default(); + let addr = Address::new(domain); + let esni_bytes = resolve_esni(dns_config, opts, &addr); + let esni_hs = rustls::esni::create_esni_handshake(&esni_bytes).unwrap(); + + let mut config = rustls::esni::create_esni_config(); + config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + + + let dns_name = webpki::DNSNameRef::try_from_ascii_str(domain).unwrap(); + let mut sess = rustls::ClientSession::new_with_esni(&Arc::new(config), dns_name, esni_hs); + let mut sock = TcpStream::connect(domain.to_owned() + ":8443").unwrap(); + let mut tls = rustls::Stream::new(&mut sess, &mut sock); + let host_header = format!("Host: {}\r\n", domain); + let mut headers = String::new(); + headers.push_str("GET / HTTP/1.1\r\n"); + headers.push_str(host_header.as_str()); + headers.push_str("Connection: close\r\n"); + headers.push_str("Accept-Encoding: identity\r\n"); + headers.push_str("\r\n"); + match tls.write(headers.as_bytes()) { + Ok(size) => { + println!("Received: {} bytes", size); + } , + Err(e) => { + println!("Error: {:?}", e); + return; + } + } + let ciphersuite = tls.sess.get_negotiated_ciphersuite().unwrap(); + writeln!(&mut std::io::stderr(), "\n\nNegotiated ciphersuite: {:?}", ciphersuite.suite).unwrap(); + let mut plaintext = Vec::new(); + match tls.read_to_end(&mut plaintext) { + Ok(success) => { + println!("read bytes: {}", success); + }, + Err(e) => { + stdout().write_all(&plaintext).unwrap(); + + println!("failure to read the bytes: {:?}", e); + + return; + } + } + stdout().write_all(&plaintext).unwrap(); +} + +pub fn resolve_esni(config: ResolverConfig, opts: ResolverOpts, address: &Address) -> Vec { + let resolver = Resolver::new(config, opts).unwrap(); + + let txt = resolver.txt_lookup(&address.esni_address()).unwrap(); + let text = Vec::from_iter(txt.iter()); + let mut bytes: Vec = Vec::new(); + for txt_record in text.iter() { + for byte_slice in txt_record.txt_data().iter() { + for byte in byte_slice.iter() { + bytes.push(*byte); + } + } + } + + decode(&bytes).unwrap() +} + +pub struct Address { + domain: String +} + +impl Address { + pub fn new(domain: &str) -> Address { + Address { + domain: String::from(domain) + } + } + + pub fn esni_address(&self) -> String { + format!("_esni.{}.", self.domain) + } + + pub fn dns_address(&self) -> String { + format!("{}.", self.domain) + } +} diff --git a/rustls/Cargo.toml b/rustls/Cargo.toml index 49e7b16129a..764aa0ec5e7 100644 --- a/rustls/Cargo.toml +++ b/rustls/Cargo.toml @@ -17,6 +17,7 @@ log = { version = "0.4.4", optional = true } ring = "0.16.5" sct = "0.6.0" webpki = "0.21.0" +hex-literal = "0.2.1" [features] default = ["logging"] diff --git a/rustls/src/cipher.rs b/rustls/src/cipher.rs index 74d76bb1ba8..982d8171af8 100644 --- a/rustls/src/cipher.rs +++ b/rustls/src/cipher.rs @@ -251,7 +251,6 @@ impl Iv { Self(value) } - #[cfg(test)] pub(crate) fn value(&self) -> &[u8; 12] { &self.0 } } diff --git a/rustls/src/client/common.rs b/rustls/src/client/common.rs index 0c22197a1ea..5eb16b2e39a 100644 --- a/rustls/src/client/common.rs +++ b/rustls/src/client/common.rs @@ -16,6 +16,7 @@ use crate::log::trace; use webpki; use std::mem; +use crate::esni::ESNIHandshakeData; pub struct ServerCertDetails { pub cert_chain: CertificatePayload, @@ -60,11 +61,14 @@ pub struct HandshakeDetails { pub session_id: SessionID, pub sent_tls13_fake_ccs: bool, pub dns_name: webpki::DNSName, + pub esni: Option, pub extra_exts: Vec, } impl HandshakeDetails { - pub fn new(host_name: webpki::DNSName, extra_exts: Vec) -> HandshakeDetails { + pub fn new(host_name: webpki::DNSName, + esni: Option, + extra_exts: Vec) -> HandshakeDetails { HandshakeDetails { resuming_session: None, transcript: hash_hs::HandshakeHash::new(), @@ -74,6 +78,7 @@ impl HandshakeDetails { session_id: SessionID::empty(), sent_tls13_fake_ccs: false, dns_name: host_name, + esni, extra_exts, } } diff --git a/rustls/src/client/hs.rs b/rustls/src/client/hs.rs index 872c44114bf..e035cc54037 100644 --- a/rustls/src/client/hs.rs +++ b/rustls/src/client/hs.rs @@ -10,7 +10,7 @@ use crate::msgs::handshake::{ProtocolNameList, ConvertProtocolNameList}; use crate::msgs::handshake::HelloRetryRequest; use crate::msgs::handshake::{CertificateStatusRequest, SCTList}; use crate::msgs::enums::{PSKKeyExchangeMode, ECPointFormat}; -use crate::msgs::codec::{Codec, Reader}; +use crate::msgs::codec::{Codec, Reader, encode_vec_u16}; use crate::msgs::persist; use crate::client::ClientSessionImpl; use crate::session::SessionSecrets; @@ -35,6 +35,7 @@ use crate::client::common::{ClientHelloDetails, ReceivedTicketDetails}; use crate::client::{tls12, tls13}; use webpki; +use crate::esni::ESNIHandshakeData; macro_rules! extract_handshake( ( $m:expr, $t:path ) => ( @@ -132,9 +133,11 @@ struct InitialState { } impl InitialState { - fn new(host_name: webpki::DNSName, extra_exts: Vec) -> InitialState { + fn new(host_name: webpki::DNSName, + esni: Option, + extra_exts: Vec) -> InitialState { InitialState { - handshake: HandshakeDetails::new(host_name, extra_exts), + handshake: HandshakeDetails::new(host_name, esni, extra_exts), } } @@ -149,8 +152,9 @@ impl InitialState { pub fn start_handshake(sess: &mut ClientSessionImpl, host_name: webpki::DNSName, + esni: Option, extra_exts: Vec) -> NextState { - InitialState::new(host_name, extra_exts) + InitialState::new(host_name, esni, extra_exts) .emit_initial_client_hello(sess) } @@ -215,9 +219,36 @@ fn emit_client_hello_for_retry(sess: &mut ClientSessionImpl, if !supported_versions.is_empty() { exts.push(ClientExtension::SupportedVersions(supported_versions)); } - if sess.config.enable_sni { + + let keyshare_entries = if support_tls13 { + let ks = tls13::choose_kx_groups(sess, &mut hello, &mut handshake, retryreq); + let ret = ks.clone(); + exts.push(ClientExtension::KeyShare(ks)); + Some(ret) + } else { + None + }; + + if sess.config.enable_sni && sess.config.encrypt_sni { + if let Some(esni) = &handshake.esni { + if let Some(ks) = &keyshare_entries { + let mut ks_bytes= Vec::new(); + encode_vec_u16(&mut ks_bytes, ks); + let esni_ext = ClientExtension::make_esni(handshake.dns_name.as_ref(), esni, ks_bytes, &handshake.randoms); + if let Some(ext) = esni_ext { + exts.push(ext); + } + + // TODO: what if ESNI fails? + } + } + + // TODO: what if ESNI is configured but there's no ESNI record? + + } else if sess.config.enable_sni { exts.push(ClientExtension::make_sni(handshake.dns_name.as_ref())); } + exts.push(ClientExtension::ECPointFormats(ECPointFormatList::supported())); exts.push(ClientExtension::NamedGroups(suites::KeyExchange::supported_groups().to_vec())); exts.push(ClientExtension::SignatureAlgorithms(verify::supported_verify_schemes().to_vec())); @@ -228,10 +259,6 @@ fn emit_client_hello_for_retry(sess: &mut ClientSessionImpl, exts.push(ClientExtension::SignedCertificateTimestampRequest); } - if support_tls13 { - tls13::choose_kx_groups(sess, &mut exts, &mut hello, &mut handshake, retryreq); - } - if let Some(cookie) = retryreq.and_then(HelloRetryRequest::get_cookie) { exts.push(ClientExtension::Cookie(cookie.clone())); } diff --git a/rustls/src/client/mod.rs b/rustls/src/client/mod.rs index 7705bd9692f..25c49f0cd59 100644 --- a/rustls/src/client/mod.rs +++ b/rustls/src/client/mod.rs @@ -13,6 +13,7 @@ use crate::anchors; use crate::sign; use crate::error::TLSError; use crate::key; +use crate::esni::ESNIHandshakeData; use crate::vecbuf::WriteV; #[cfg(feature = "logging")] use crate::log::trace; @@ -124,6 +125,12 @@ pub struct ClientConfig { /// The default is true. pub enable_sni: bool, + /// If true, encrypt the SNI. Only used if `enable_sni` is true, in which case + /// the clear text SNI will not be sent. + /// + /// The default is false. + pub encrypt_sni: bool, + /// How to verify the server certificate chain. verifier: Arc, @@ -160,6 +167,7 @@ impl ClientConfig { versions: vec![ProtocolVersion::TLSv1_3, ProtocolVersion::TLSv1_2], ct_logs: None, enable_sni: true, + encrypt_sni: false, verifier: Arc::new(verify::WebPKIVerifier::new()), key_log: Arc::new(NoKeyLog {}), enable_early_data: false, @@ -397,8 +405,8 @@ impl ClientSessionImpl { } } - pub fn start_handshake(&mut self, hostname: webpki::DNSName, extra_exts: Vec) { - self.state = Some(hs::start_handshake(self, hostname, extra_exts)); + pub fn start_handshake(&mut self, hostname: webpki::DNSName, esni: Option, extra_exts: Vec) { + self.state = Some(hs::start_handshake(self, hostname, esni, extra_exts)); } pub fn get_cipher_suites(&self) -> Vec { @@ -596,7 +604,17 @@ impl ClientSession { /// hostname of who we want to talk to. pub fn new(config: &Arc, hostname: webpki::DNSNameRef) -> ClientSession { let mut imp = ClientSessionImpl::new(config); - imp.start_handshake(hostname.into(), vec![]); + imp.start_handshake(hostname.into(), None,vec![]); + ClientSession { imp } + } + + /// Make a new ClientSession. `config` controls how + /// we behave in the TLS protocol, `hostname` is the + /// hostname of who we want to talk to, and esni_keys are used to + /// encrypt the hostname in the ClientHello. + pub fn new_with_esni(config: &Arc, hostname: webpki::DNSNameRef, esni: ESNIHandshakeData) -> ClientSession { + let mut imp = ClientSessionImpl::new(config); + imp.start_handshake(hostname.into(), Some(esni), vec![]); ClientSession { imp } } diff --git a/rustls/src/client/tls13.rs b/rustls/src/client/tls13.rs index 6b410fcbb34..6484cc1361f 100644 --- a/rustls/src/client/tls13.rs +++ b/rustls/src/client/tls13.rs @@ -82,10 +82,9 @@ fn save_kx_hint(sess: &mut ClientSessionImpl, dns_name: webpki::DNSNameRef, grou } pub fn choose_kx_groups(sess: &mut ClientSessionImpl, - exts: &mut Vec, hello: &mut ClientHelloDetails, handshake: &mut HandshakeDetails, - retryreq: Option<&HelloRetryRequest>) { + retryreq: Option<&HelloRetryRequest>) -> Vec { // Choose our groups: // - if we've been asked via HelloRetryRequest for a specific // one, do that. @@ -115,7 +114,7 @@ pub fn choose_kx_groups(sess: &mut ClientSessionImpl, } } - exts.push(ClientExtension::KeyShare(key_shares)); + key_shares } /// This implements the horrifying TLS1.3 hack where PSK binders have a diff --git a/rustls/src/esni.rs b/rustls/src/esni.rs new file mode 100644 index 00000000000..0152b52a0a6 --- /dev/null +++ b/rustls/src/esni.rs @@ -0,0 +1,459 @@ +use crate::client::ClientConfig; +use crate::msgs::handshake::{ESNIRecord, KeyShareEntry, ServerNamePayload, ServerName, ClientEncryptedSNI, ESNIContents, Random, PaddedServerNameList, ClientESNIInner}; +use crate::msgs::enums::ServerNameType; +use crate::msgs::enums::ProtocolVersion; +use crate::suites::{KeyExchange, TLS13_CIPHERSUITES, choose_ciphersuite_preferring_server}; +use crate::msgs::codec::{Codec, Reader}; +use crate::rand; + +use std::time::{SystemTime, UNIX_EPOCH}; + +use ring::{digest, hkdf}; +use webpki; +use crate::SupportedCipherSuite; +use crate::msgs::base::PayloadU16; +use ring::hkdf::Prk; +use crate::cipher::{Iv, IvLen}; +use ring::aead::UnboundKey; +use crate::session::SessionRandoms; +use crate::key_schedule::hkdf_expand; + +/// Data calculated for a client session from a DNS ESNI record. +#[derive(Clone, Debug)] +pub struct ESNIHandshakeData { + /// The selected Key Share from the DNS record + pub peer_share: KeyShareEntry, + + /// The selected CipherSuite from the DNS record + pub cipher_suite: &'static SupportedCipherSuite, + + /// The length to pad the ESNI to + pub padded_length: u16, + + /// A digest of the DNS record + pub record_digest: Vec, +} + +/// Create a TLS 1.3 Config for ESNI +pub fn create_esni_config() -> ClientConfig { + let mut config = ClientConfig::new(); + config.versions = vec![ProtocolVersion::TLSv1_3]; + config.ciphersuites = TLS13_CIPHERSUITES.to_vec(); + config.encrypt_sni = true; + config +} + +/// Creates a `ClientConfig` with defaults suitable for ESNI extension support. +/// This creates a config that supports TLS 1.3 only. +pub fn create_esni_handshake(record_bytes: &Vec) -> Option { + let record = ESNIRecord::read(&mut Reader::init(&record_bytes))?; + // Check whether the record is still valid + let now = now()?; + + if now < record.not_before || now > record.not_after { + return None + } + + record_to_handshake_data(&record, record_bytes) +} + +fn record_to_handshake_data(record: &ESNIRecord, record_bytes: &Vec) -> Option { + let peer_share = match KeyExchange::supported_groups() + .iter() + .flat_map(|group| { + record.keys.iter().find(|key| { key.group == *group }) + }).nth(0) + .cloned() { + Some(entry) => entry, + None => return None, + }; + + let cipher_suite= match + choose_ciphersuite_preferring_server(record.cipher_suites.as_slice(), + &TLS13_CIPHERSUITES) { + Some(entry) => entry, + None => return None, + }; + + Some(ESNIHandshakeData { + peer_share, + cipher_suite, + padded_length: record.padded_length, + record_digest: record_digest(cipher_suite.get_hash(), record_bytes), + }) +} + +fn now() -> Option { + let start = SystemTime::now(); + match start.duration_since(UNIX_EPOCH) { + Err(_e) => None, + Ok(since_the_epoch) => Some(since_the_epoch.as_secs()) + } +} + +fn record_digest(algorithm: &'static ring::digest::Algorithm, bytes: &[u8]) -> Vec { + digest::digest(algorithm, bytes).as_ref().to_vec() +} + +/// Compute the encrypted SNI +pub fn compute_esni(dns_name: webpki::DNSNameRef, + hs_data: &ESNIHandshakeData, + key_share_bytes: Vec, + randoms: &SessionRandoms) -> Option { + let mut nonce = [0u8; 16]; + rand::fill_random(&mut nonce); + let mut sni_bytes = compute_client_esni_inner(dns_name, hs_data.padded_length, nonce); + let mut peer_bytes = Vec::new(); + hs_data.peer_share.clone().encode(&mut peer_bytes); + + let key_exchange = match KeyExchange::start_ecdhe(hs_data.peer_share.group) { + Some(ke) => ke, + None => return None, + }; + let exchange_result = key_exchange.complete(&hs_data.peer_share.payload.0)?; + let contents_bytes = compute_esni_content(&hs_data, &exchange_result.pubkey.clone().as_ref().to_vec(), randoms.client); + let hash = esni_hash(&contents_bytes, hs_data.cipher_suite.get_hash()); + + let zx = zx(hs_data.cipher_suite.hkdf_algorithm, &exchange_result.premaster_secret); + let key = hkdf_expand(&zx, hs_data.cipher_suite.get_aead_alg(), b"esni key", &hash); + let iv: Iv = hkdf_expand(&zx, IvLen, b"esni iv", &hash); + let aad = ring::aead::Aad::from(key_share_bytes.to_vec()); + + match encrypt(key, iv, aad, &mut sni_bytes) { + Some(bytes) => { + Some (ClientEncryptedSNI { + suite: hs_data.cipher_suite.suite, + key_share_entry: KeyShareEntry::new(hs_data.peer_share.group, exchange_result.pubkey.as_ref()), + record_digest: PayloadU16(hs_data.record_digest.clone()), + encrypted_sni: PayloadU16(bytes), + }) + }, + _ => None + } +} + +fn compute_esni_content(hs_data: &ESNIHandshakeData, pubkey: &Vec, random: [u8; 32]) -> Vec { + let contents = ESNIContents { + record_digest: PayloadU16::new(hs_data.record_digest.clone()), + esni_key_share: KeyShareEntry { + group: hs_data.peer_share.group, + payload: PayloadU16(pubkey.to_vec()) + }, + client_hello_random: Random::from_slice(&random), + }; + + let mut contents_bytes = Vec::new(); + contents.encode(&mut contents_bytes); + contents_bytes +} + +fn compute_client_esni_inner(dns_name: webpki::DNSNameRef, length: u16, nonce: [u8; 16]) -> Vec { + let name = ServerName { + typ: ServerNameType::HostName, + payload: ServerNamePayload::HostName(dns_name.into()), + }; + let psnl = PaddedServerNameList::new(vec![name], length); + + let mut padded_bytes = Vec::new(); + psnl.encode(&mut padded_bytes); + + let client_esni_inner = ClientESNIInner { + nonce, + real_sni: psnl, + }; + let mut sni_bytes = Vec::new(); + client_esni_inner.encode(&mut sni_bytes); + sni_bytes +} + +fn esni_hash(encoded_esni_contents: &Vec, algorithm: &'static ring::digest::Algorithm) -> Vec { + let digest = digest::digest(algorithm, &encoded_esni_contents); + digest.as_ref().to_vec() +} + +fn zx(algorithm: ring::hkdf::Algorithm, secret: &Vec) -> Prk { + let salt = hkdf::Salt::new(algorithm, &[]); + salt.extract(secret) +} + +fn encrypt(unbound: UnboundKey, iv: Iv, aad: ring::aead::Aad>, sni_bytes: &mut Vec) -> Option> { + let lsk = ring::aead::LessSafeKey::new(unbound); + match lsk.seal_in_place_append_tag(ring::aead::Nonce::assume_unique_for_key(*iv.value()), + aad, + sni_bytes) { + Ok(_) => Some(sni_bytes.clone()), + _ => None + } +} + +#[cfg(test)] +mod tests { + use crate::key_schedule::hkdf_expand; + use crate::cipher::{Iv, IvLen}; + use crate::msgs::handshake::ESNIRecord; + use crate::msgs::codec::{Codec, Reader}; + use webpki::DNSNameRef; + + #[test] + fn test_compute_esni_content() { + let esni_keys = hex!(" + ff 01 a0 1f 3e 02 00 24 00 1d 00 20 f6 27 6f e9 + c5 63 14 c3 d7 0c 12 ce ab ea 55 97 28 9e f3 75 + ee 5a ae 04 41 af 5b ff d4 fa 78 5f 00 02 13 01 + 01 04 00 00 00 00 5d da 02 98 00 00 00 00 5d da + 17 b0 00 00 + "); + + let random = hex!(" + 8a 96 02 a9 3c 80 de 46 01 22 c8 0a 65 48 fb c2 + b7 f6 be 87 07 8d 2d d8 e8 68 25 aa c3 44 1d 7f + "); + + let pubkey = hex!(" + b3 cf b7 0e eb e5 d4 40 c7 00 af 31 + ba 1e 32 a4 8a ce 7d 6d 24 ce ed 33 4b 82 b4 c9 + 4e 90 78 17 + "); + + let expected = hex!(" + 00 20 20 4f 08 28 be 7f 73 0e 92 bb 2b 1d 0e 3c + 35 05 86 1d 83 5a a8 7b ad fa 83 3b 17 9a f9 70 + 42 c9 00 1d 00 20 b3 cf b7 0e eb e5 d4 40 c7 00 + af 31 ba 1e 32 a4 8a ce 7d 6d 24 ce ed 33 4b 82 + b4 c9 4e 90 78 17 8a 96 02 a9 3c 80 de 46 01 22 + c8 0a 65 48 fb c2 b7 f6 be 87 07 8d 2d d8 e8 68 + 25 aa c3 44 1d 7f + "); + + let record = ESNIRecord::read(&mut Reader::init(&esni_keys)).unwrap(); + let hs_data = super::record_to_handshake_data(&record, &esni_keys.to_vec()).unwrap(); + let esni_contents = super::compute_esni_content(&hs_data, &pubkey.to_vec(), random); + assert!(crate::msgs::handshake::slice_eq(&expected, &esni_contents)); + } + + #[test] + fn test_record_digest() { + let esni_keys = hex!(" + ff 01 f3 92 e6 e7 00 24 00 1d 00 20 10 9f e6 de + ac e8 f6 2f 94 61 9c 1d 61 c9 a2 b9 2f 45 92 3d + aa 93 87 e4 e5 51 39 e7 da 26 2b 65 00 02 13 01 + 01 04 00 00 00 00 5d d9 f4 88 00 00 00 00 5d da + 09 a0 00 00 + "); + + let expected = hex!(" + 3b d7 25 90 a7 58 68 16 46 c5 22 93 2a 1e b0 8d + 0c e3 8c 2c 67 21 8e bf ab 88 90 04 49 cc 23 92 + "); + + let result = super::record_digest(&ring::digest::SHA256, &esni_keys); + assert!(crate::msgs::handshake::slice_eq(&expected, &result)); + } + + #[test] + fn test_compute_client_esni_inner() { + let nonce = hex!("c0 2b f3 39 f8 95 58 ac c4 7c d1 c6 b1 ff a7 28"); + + let dns_name = DNSNameRef::try_from_ascii(b"canbe.esni.defo.ie").unwrap(); + + let expected = hex!(" + c0 2b f3 39 f8 95 58 ac c4 7c d1 c6 b1 ff a7 28 + 00 15 00 00 12 63 61 6e 62 65 2e 65 73 6e 69 2e + 64 65 66 6f 2e 69 65 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 + "); + + let result = super::compute_client_esni_inner(dns_name, 260u16, nonce); + assert_eq!(expected.len(), result.len()); + assert!(crate::msgs::handshake::slice_eq(&expected, &result)); + } + + #[test] + fn test_hash() { + let esni_bytes = hex!(" + 00 20 3e 06 06 98 4c 3b a9 70 3a fb a7 a1 2d 75 + 29 5b 05 81 7d 75 8f 40 9b 51 00 c8 37 8e 9d 08 + 7e f1 00 1d 00 20 72 d8 3a 31 da 1c cd c7 e5 89 + c1 c6 24 bd 7a 14 2d 90 de 7f 01 82 73 9d 25 14 + c2 66 e1 97 23 5b 64 c0 c4 7c 5b c8 14 a0 a4 2b + 0c 2f f4 23 51 00 10 f4 1d f4 c1 f4 3c 3e 89 c8 + fe 87 25 d1 9f 00 + "); + + let expected = hex!(" + 21 5b ba fe a8 9e da 35 7b 7b 55 e4 6d 01 ac c8 + 94 94 b2 6e e6 55 08 0e 47 21 6a b2 3b 7d 25 f7 + "); + + let result = super::esni_hash(&esni_bytes.to_vec(), &ring::digest::SHA256); + assert!(crate::msgs::handshake::slice_eq(&expected, &result)); + } + + #[test] + fn test_zx() { + let z_bytes = hex!(" + de cf 6a 8c 23 49 e1 8c db d8 48 49 7c 10 16 9a + 77 66 fb 3f f4 8b 54 f7 bd 1f 15 14 74 e1 88 1c"); + + let hash = hex!(" + a5 33 9b 1b a6 ae d2 7f 43 b9 91 5e 5e bc 8e 5a + af d9 fb 1d e2 b4 df 36 13 70 97 14 27 a1 61 25 + "); + + let expected_iv = hex!(" + 07 d7 77 4c 69 be bd ad 1b 75 49 c7 + "); + + let aad_bytes = hex!(" + 00 69 00 1d 00 20 70 cb + 7e ce 36 ab c1 b6 e1 92 6a 9a f2 08 d9 91 70 f1 + 98 7a aa 0f e3 9b f0 b3 c5 4d 79 00 a8 07 00 17 + 00 41 04 03 1d 6c 6c e6 f3 28 1f 6f f2 78 d5 5c + 0f 5e f7 be 52 71 9f 7e c0 0e 6e 26 db 85 7b f9 + e0 73 91 e6 b5 3e 06 7b ef c8 f8 b5 f0 46 16 c2 + 9f 0d 52 c3 6a 9e 41 2f 68 ce 7e ee d0 27 99 e5 + 28 aa 9e + "); + + let plain_text = hex!(" + 4f b0 25 11 6b f7 4d f8 ce f3 0f 59 ce d9 d6 df + 00 17 00 00 14 63 64 6e 6a 73 2e 63 6c 6f 75 64 + 66 6c 61 72 65 2e 63 6f 6d 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 + "); + + let expected = hex!(" + 28 0a 3d 56 cd 30 9d 68 ac 98 1b 41 bb fb 85 26 + 48 ef 1a 83 c8 aa bd 12 15 80 44 10 50 2f c0 3d + 68 15 99 e0 47 6a 80 c2 e9 a0 df 86 16 7e a8 a4 + 37 8c 27 62 89 7e f8 60 4f 04 cf b5 ea 60 ed 99 + 51 59 70 a1 a5 ac b9 32 7d 35 86 e9 e2 01 d6 60 + 9d 8d de 81 03 69 13 dd 66 09 e9 18 76 f9 25 65 + 3d b7 ea 22 50 da 50 4d d8 74 31 5a 35 a2 29 7a + 09 31 0a 45 4e b2 29 fd 72 40 04 93 3a e3 a6 7d + 09 46 bb b5 8d e0 0f b5 12 e4 36 7d 38 32 3b b5 + ee 99 6f ad 2c ea af 39 9f a1 dc c9 70 dc 2f ad + 46 de 2a d6 8c 4e 3c e6 31 01 8a 97 f0 1f c9 3c + b8 c8 f1 45 02 c4 d7 3d ee b9 88 6f 53 cc 85 0b + 69 ce 61 dc 30 c8 85 2d e1 d0 d3 d6 10 c2 32 04 + 0d 96 2d d5 4a a4 1f e2 bc a3 77 15 72 61 20 75 + aa 9b 4a ee f7 25 cf 22 95 b9 77 88 48 f3 30 8e + a4 ab 3d b4 bd b4 e4 24 98 b7 ca 7e bf 26 ee 82 + b5 b4 fd f2 f0 65 04 ea 4c 7c 75 25 24 b0 be 92 + 9a a2 b7 e4 82 5a 37 cf 08 3f 0e 9b 6c 89 27 b4 + 33 15 75 24 + "); + + let hkdf_alg = crate::suites::TLS13_AES_128_GCM_SHA256.hkdf_algorithm; + let zx = super::zx(hkdf_alg, &z_bytes.to_vec()); + let aead_alg = crate::suites::TLS13_AES_128_GCM_SHA256.get_aead_alg(); + let key = hkdf_expand(&zx, aead_alg, b"esni key", hash.as_ref()); + + let iv: Iv = hkdf_expand(&zx, IvLen, b"esni iv", hash.as_ref()); + assert!(crate::msgs::handshake::slice_eq(&expected_iv, iv.value())); + + let aad = ring::aead::Aad::from(aad_bytes.to_vec()); + let mut sni_bytes = Vec::from(plain_text.to_vec()); + let encrypted = super::encrypt(key, iv, aad, &mut sni_bytes).unwrap(); + + assert!(crate::msgs::handshake::slice_eq(&expected, &encrypted)); + } + + #[test] + fn test_encrypt() { + let key_bytes = hex!("9b 9f f2 2c dd 39 4c f6 20 ac f8 d6 f6 90 99 ab"); + + let iv_bytes = hex!("d0 c2 2c 42 3c 03 a7 1d 3d 36 36 51"); + + let aad_bytes = hex!(" + 00 69 00 1d 00 20 e7 41 + 94 4b 78 8d 6f cd 6b 5b 64 f6 69 35 83 d1 df c7 + e8 21 55 c6 f7 8d a5 c3 25 b9 7a 69 58 7d 00 17 + 00 41 04 d8 75 ac 7c 46 38 c6 eb 35 a9 90 60 6b + 1b be b1 70 dd 18 0c 80 82 8d 83 95 b1 aa a5 2e + 24 2e fb ed 9f 2a bd 7f 86 f0 8c 8b 6b ca db a6 + 28 69 88 1d fb 76 5f 34 d9 da 0b 07 02 64 80 d2 + d3 84 15 + "); + + let plain_text = hex!(" + ad 1b f4 b3 d3 14 59 48 59 9e be c8 56 42 4f 66 + 00 15 00 00 12 63 61 6e 62 65 2e 65 73 6e 69 2e + 64 65 66 6f 2e 69 65 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + 00 00 00 00 + "); + + let expected = hex!(" + 6f f6 5d 1e bd 9c 35 2d 2c 1c ca 92 5d 3e 1a 65 + f6 30 fe 97 3b a0 24 9d 92 b8 cb 67 f0 1d 17 a4 + bc 11 9b ac 39 c4 48 f7 bb 86 04 b5 58 ad 76 15 + 10 c3 21 d0 3b 86 ac c9 d6 7e 9f 89 6e b0 73 cb + 69 97 f4 1b f5 17 e9 81 29 86 6f 3e df 49 99 3c + 59 00 24 6c 2d d6 3e 7b d2 b7 bd 3a c0 90 8f b6 + dc 2b 11 08 15 00 41 ca fb 79 ef 57 5b 17 18 00 + bf c2 0c 1b 2b cf 1e 9b f0 0f 9d 67 32 37 e1 06 + 22 f8 cb a8 a3 40 26 6e 50 85 32 29 d7 20 41 a5 + 0f 47 87 d0 af 01 ba 83 62 ad a0 b6 ac 8e d5 dd + 24 42 3d f8 a8 f9 9e 16 40 cf 85 b9 16 39 f8 94 + 4b bd cb a5 59 a8 a9 65 7a 83 95 b2 38 c7 3b d5 + d4 9b 6f f0 e3 18 d0 cb 65 65 c9 0c 8a 07 a1 ce + 5f 39 ed 6a 1b 6f e7 59 11 7d b3 81 e4 4b 51 d4 + db 28 f3 95 eb 16 62 de de 29 c7 dc 79 54 67 24 + d7 4d d1 3f 34 ca 64 6e 6c 12 9a e4 0c 1c ea 33 + c3 81 15 48 04 14 a4 ed ab 44 90 e9 0d c2 56 8a + df 4e 92 eb 3b 93 f5 5c 59 15 0e 7d 85 66 2d b4 + 62 ee 41 8a + "); + + let alg= crate::suites::TLS13_AES_128_GCM_SHA256.get_aead_alg(); + let key = ring::aead::UnboundKey::new(alg, &key_bytes).unwrap(); + let iv = crate::cipher::Iv::new(iv_bytes); + let aad = ring::aead::Aad::from(aad_bytes.to_vec()); + let mut sni_bytes = Vec::from(plain_text.to_vec()); + let encrypted = super::encrypt(key, iv, aad, &mut sni_bytes).unwrap(); + assert_eq!(expected.len(), encrypted.len()); + assert!(crate::msgs::handshake::slice_eq(&expected, encrypted.as_slice())); + } +} diff --git a/rustls/src/lib.rs b/rustls/src/lib.rs index b8a6d6e1155..9899a708f6a 100644 --- a/rustls/src/lib.rs +++ b/rustls/src/lib.rs @@ -238,6 +238,9 @@ mod key; mod bs_debug; mod keylog; +/// ESNI related functions +pub mod esni; + /// Internal classes which may be useful outside the library. /// The contents of this section DO NOT form part of the stable interface. pub mod internal { @@ -299,3 +302,6 @@ pub use crate::verify::{ServerCertVerifier, ServerCertVerified, #[cfg(feature = "dangerous_configuration")] pub use crate::client::danger::DangerousClientConfig; +#[cfg(test)] +#[macro_use] +extern crate hex_literal; diff --git a/rustls/src/msgs/enums.rs b/rustls/src/msgs/enums.rs index f3e4d040daa..19316c6bc30 100644 --- a/rustls/src/msgs/enums.rs +++ b/rustls/src/msgs/enums.rs @@ -235,7 +235,8 @@ enum_builder! { NextProtocolNegotiation => 0x3374, ChannelId => 0x754f, RenegotiationInfo => 0xff01, - TransportParameters => 0xffa5 + TransportParameters => 0xffa5, + EncryptedServerName => 0xffce } } @@ -789,3 +790,12 @@ enum_builder! { OCSP => 0x01 } } + +enum_builder! { + /// The `ESNI` protocol version. + @U16 + EnumName: ESNIVersion; + EnumVal{ + V1 => 0xff01 + } +} diff --git a/rustls/src/msgs/handshake.rs b/rustls/src/msgs/handshake.rs index e342dbe2a03..bc0c907988b 100644 --- a/rustls/src/msgs/handshake.rs +++ b/rustls/src/msgs/handshake.rs @@ -3,12 +3,14 @@ use crate::msgs::enums::{CipherSuite, Compression, ExtensionType, ECPointFormat} use crate::msgs::enums::{HashAlgorithm, SignatureAlgorithm, ServerNameType}; use crate::msgs::enums::{SignatureScheme, KeyUpdateRequest, NamedGroup}; use crate::msgs::enums::{ClientCertificateType, CertificateStatusType}; +use crate::msgs::enums::ESNIVersion; use crate::msgs::enums::ECCurveType; use crate::msgs::enums::PSKKeyExchangeMode; use crate::msgs::base::{Payload, PayloadU8, PayloadU16, PayloadU24}; use crate::msgs::codec; use crate::msgs::codec::{Codec, Reader}; use crate::key; +use crate::esni::*; #[cfg(feature = "logging")] use crate::log::warn; @@ -17,7 +19,9 @@ use std::fmt; use std::io::Write; use std::collections; use std::mem; +use ring::digest; use webpki; +use crate::session::SessionRandoms; macro_rules! declare_u8_vec( ($name:ident, $itemtype:ty) => { @@ -329,6 +333,223 @@ impl ConvertServerNameList for ServerNameRequest { } } +// --- TLS 1.3 Encrypted SNI + +declare_u16_vec!(CipherSuites, CipherSuite); + +#[derive(Clone, Debug)] +pub struct ESNIRecord { + pub version: ESNIVersion, + pub checksum: Vec, + pub checksum_valid: bool, + pub keys: KeyShareEntries, + pub cipher_suites: CipherSuites, + pub padded_length: u16, + pub not_before: u64, + pub not_after: u64, + pub extensions: PayloadU16, +} + +impl ESNIRecord { + pub fn is_checksum_valid(&self) -> bool { + self.checksum_valid + } +} + +impl Codec for ESNIRecord { + fn encode(&self, bytes: &mut Vec) { + self.version.encode(bytes); + for byte in self.checksum.iter() { + byte.encode(bytes); + } + self.keys.encode(bytes); + self.cipher_suites.encode(bytes); + self.padded_length.encode(bytes); + self.not_before.encode(bytes); + self.not_after.encode(bytes); + self.extensions.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let version = ESNIVersion::read(r)?; + let checksum: Vec = u32::read(r)?.get_encoding(); + + // checksum + let mut ctx = digest::Context::new(&digest::SHA256); + ctx.update(version.get_u16().get_encoding().as_slice()); + ctx.update(&[0u8, 0u8, 0u8, 0u8]); + let rest = r.rest(); + ctx.update(rest); + let digest = ctx.finish(); + let checksum_valid = slice_eq(checksum.as_slice(), &digest.as_ref()[0..4]); + + let tail_reader = &mut Reader::init(rest); + Some(ESNIRecord { + version, + checksum, + checksum_valid, + keys: KeyShareEntries::read(tail_reader)?, + cipher_suites: CipherSuites::read(tail_reader)?, + padded_length: u16::read(tail_reader)?, + not_before: u64::read(tail_reader)?, + not_after: u64::read(tail_reader)?, + extensions: PayloadU16::read(tail_reader)?, + }) + } +} + +pub fn slice_eq<'a, T: PartialEq>(a: &'a [T], b: &'a [T]) -> bool { + if a.len() != b.len() { + return false; + } + + for i in 0..a.len() { + if a[i] != b[i] { + return false; + } + } + + true +} + +#[derive(Clone, Debug)] +pub struct ClientEncryptedSNI { + pub suite: CipherSuite, + pub key_share_entry: KeyShareEntry, + pub record_digest: PayloadU16, + pub encrypted_sni: PayloadU16, +} + +impl ClientEncryptedSNI { + pub fn new(suite: CipherSuite, + key_share_entry: KeyShareEntry, + record_digest: PayloadU16, + encrypted_sni: PayloadU16) -> ClientEncryptedSNI { + ClientEncryptedSNI { + suite, + key_share_entry, + record_digest, + encrypted_sni + } + } +} + +impl Codec for ClientEncryptedSNI { + fn encode(&self, bytes: &mut Vec) { + self.suite.encode(bytes); + self.key_share_entry.encode(bytes); + self.record_digest.encode(bytes); + self.encrypted_sni.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let suite = CipherSuite::read(r)?; + let key_share_entry = KeyShareEntry::read(r)?; + let record_digest = PayloadU16::read(r)?; + let encrypted_sni = PayloadU16::read(r)?; + + Some(ClientEncryptedSNI { + suite, + key_share_entry, + record_digest, + encrypted_sni + }) + } +} + +#[derive(Clone, Debug)] +pub struct ESNIContents { + pub record_digest: PayloadU16, + pub esni_key_share: KeyShareEntry, + pub client_hello_random: Random, +} + +impl Codec for ESNIContents { + fn encode(&self, bytes: &mut Vec) { + self.record_digest.encode(bytes); + self.esni_key_share.encode(bytes); + self.client_hello_random.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + Some(ESNIContents { + record_digest: PayloadU16::read(r)?, + esni_key_share: KeyShareEntry::read(r)?, + client_hello_random: Random::read(r)?, + }) + } +} + +#[derive(Clone, Debug)] +pub struct PaddedServerNameList { + pub sni: ServerNameRequest, + pub zeros: Vec, + pub padded_length: u16, +} + +impl PaddedServerNameList { + pub fn new(sni: ServerNameRequest, padded_length: u16) -> PaddedServerNameList { + let mut output = Vec::new(); + sni.encode(&mut output); + let length = padded_length - output.len() as u16; + PaddedServerNameList { + sni, + zeros: vec![0; length as usize], + padded_length, + } + } +} + +impl Codec for PaddedServerNameList { + fn encode(&self, bytes: &mut Vec) { + self.sni.encode(bytes); + bytes.extend_from_slice(self.zeros.as_slice()); + } + + fn read(r: &mut Reader) -> Option { + let count = r.left(); + let sni = ServerName::read(r)?; + let sni_length = count - r.left(); + let mut padding = Vec::with_capacity(r.left()); + padding.extend_from_slice(r.rest()); + let len = padding.len(); + for zero in padding.iter() { + if *zero != 0u8 { + return None; + } + } + + Some(PaddedServerNameList { + sni: vec![sni], + zeros: padding, + padded_length: (sni_length + len) as u16, + }) + } +} + +#[derive(Clone, Debug)] +pub struct ClientESNIInner { + pub nonce: [u8; 16], + pub real_sni: PaddedServerNameList, +} + +impl Codec for ClientESNIInner { + fn encode(&self, bytes: &mut Vec) { + bytes.extend_from_slice(&self.nonce); + self.real_sni.encode(bytes); + } + + fn read(r: &mut Reader) -> Option { + let mut nonce = [0u8; 16]; + nonce.clone_from_slice(r.take(16)?); + + Some(ClientESNIInner { + nonce, + real_sni: PaddedServerNameList::read(r)? + }) + } +} + pub type ProtocolNameList = VecU16OfPayloadU8; pub trait ConvertProtocolNameList { @@ -557,6 +778,7 @@ pub enum ClientExtension { SignedCertificateTimestampRequest, TransportParameters(Vec), EarlyData, + EncryptedServerName(ClientEncryptedSNI), Unknown(UnknownExtension), } @@ -580,6 +802,7 @@ impl ClientExtension { ClientExtension::SignedCertificateTimestampRequest => ExtensionType::SCT, ClientExtension::TransportParameters(_) => ExtensionType::TransportParameters, ClientExtension::EarlyData => ExtensionType::EarlyData, + ClientExtension::EncryptedServerName(_) => ExtensionType::EncryptedServerName, ClientExtension::Unknown(ref r) => r.typ, } } @@ -608,6 +831,7 @@ impl Codec for ClientExtension { ClientExtension::Cookie(ref r) => r.encode(&mut sub), ClientExtension::CertificateStatusRequest(ref r) => r.encode(&mut sub), ClientExtension::TransportParameters(ref r) => sub.extend_from_slice(r), + ClientExtension::EncryptedServerName(ref r) => r.encode(&mut sub), ClientExtension::Unknown(ref r) => r.encode(&mut sub), } @@ -634,6 +858,9 @@ impl Codec for ClientExtension { ExtensionType::ServerName => { ClientExtension::ServerName(ServerNameRequest::read(&mut sub)?) } + ExtensionType::EncryptedServerName => { + ClientExtension::EncryptedServerName(ClientEncryptedSNI::read(&mut sub)?) + } ExtensionType::SessionTicket => { if sub.any_left() { ClientExtension::SessionTicketOffer(Payload::read(&mut sub)?) @@ -688,6 +915,15 @@ impl ClientExtension { ClientExtension::ServerName(vec![ name ]) } + + /// Make an ESNI request, encrypting `hostname` with the ESNIRecord + pub fn make_esni(dns_name: webpki::DNSNameRef, + hs_data: &ESNIHandshakeData, + key_share_bytes: Vec, + randoms: &SessionRandoms) -> Option { + let esni = compute_esni(dns_name, hs_data, key_share_bytes, randoms)?; + Some(ClientExtension::EncryptedServerName(esni)) + } } #[derive(Clone, Debug)] diff --git a/rustls/src/msgs/handshake_test.rs b/rustls/src/msgs/handshake_test.rs index b4fc6b8e49e..163911f1dc0 100644 --- a/rustls/src/msgs/handshake_test.rs +++ b/rustls/src/msgs/handshake_test.rs @@ -4,6 +4,7 @@ use super::base::{Payload, PayloadU8, PayloadU16, PayloadU24}; use super::codec::{Reader, Codec}; use webpki::DNSNameRef; use crate::key::Certificate; +use base64; use std::mem; @@ -926,3 +927,15 @@ fn can_roundtrip_all_tls13_handshake_payloads() { println!("{:?}", other); } } + +#[test] +fn test_esni() { + // An ESNI record from Cloudflare + let base64_esni = "/wHdBX2/ACQAHQAg+Q5TFpKpR0O9dALVeNC9kDBfNwNBvzHma4VrZgMtKXwAAhMBAQQAAAAAXaSpkAAAAABdrJKQAAA="; + let bytes = base64::decode(&base64_esni).unwrap(); + let record = ESNIRecord::read(&mut Reader::init(&bytes)).unwrap(); + assert!(record.is_checksum_valid()); + let mut output = Vec::new(); + record.encode(&mut output); + assert_eq!(base64_esni, base64::encode(&output)); +} diff --git a/rustls/src/quic.rs b/rustls/src/quic.rs index 554cbf054f6..6621fa2fc00 100644 --- a/rustls/src/quic.rs +++ b/rustls/src/quic.rs @@ -152,7 +152,7 @@ pub trait ClientQuicExt { assert!(config.versions.iter().all(|x| x.get_u16() >= ProtocolVersion::TLSv1_3.get_u16()), "QUIC requires TLS version >= 1.3"); let mut imp = ClientSessionImpl::new(config); imp.common.protocol = Protocol::Quic; - imp.start_handshake(hostname.into(), vec![ + imp.start_handshake(hostname.into(), None, vec![ ClientExtension::TransportParameters(params), ]); ClientSession { imp } diff --git a/rustls/src/suites.rs b/rustls/src/suites.rs index a1ddc85d3b2..1f5c24b0c3d 100644 --- a/rustls/src/suites.rs +++ b/rustls/src/suites.rs @@ -373,6 +373,11 @@ pub static TLS13_AES_128_GCM_SHA256: SupportedCipherSuite = SupportedCipherSuite hkdf_algorithm: ring::hkdf::HKDF_SHA256, }; +pub static TLS13_CIPHERSUITES: [&'static SupportedCipherSuite; 3] = + [&TLS13_CHACHA20_POLY1305_SHA256, + &TLS13_AES_256_GCM_SHA384, + &TLS13_AES_128_GCM_SHA256]; + /// A list of all the cipher suites supported by rustls. pub static ALL_CIPHERSUITES: [&'static SupportedCipherSuite; 9] = [// TLS1.3 suites