Skip to content

Commit

Permalink
Merge aa6317d into 1d8114c
Browse files Browse the repository at this point in the history
  • Loading branch information
sayrer committed Nov 25, 2019
2 parents 1d8114c + aa6317d commit 751fcda
Show file tree
Hide file tree
Showing 15 changed files with 906 additions and 19 deletions.
5 changes: 5 additions & 0 deletions rustls-mio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
104 changes: 104 additions & 0 deletions rustls-mio/examples/esniclient.rs
Original file line number Diff line number Diff line change
@@ -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<u8> {
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<u8> = 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)
}
}
1 change: 1 addition & 0 deletions rustls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
1 change: 0 additions & 1 deletion rustls/src/cipher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ impl Iv {
Self(value)
}

#[cfg(test)]
pub(crate) fn value(&self) -> &[u8; 12] { &self.0 }
}

Expand Down
7 changes: 6 additions & 1 deletion rustls/src/client/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::log::trace;
use webpki;

use std::mem;
use crate::esni::ESNIHandshakeData;

pub struct ServerCertDetails {
pub cert_chain: CertificatePayload,
Expand Down Expand Up @@ -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<ESNIHandshakeData>,
pub extra_exts: Vec<ClientExtension>,
}

impl HandshakeDetails {
pub fn new(host_name: webpki::DNSName, extra_exts: Vec<ClientExtension>) -> HandshakeDetails {
pub fn new(host_name: webpki::DNSName,
esni: Option<ESNIHandshakeData>,
extra_exts: Vec<ClientExtension>) -> HandshakeDetails {
HandshakeDetails {
resuming_session: None,
transcript: hash_hs::HandshakeHash::new(),
Expand All @@ -74,6 +78,7 @@ impl HandshakeDetails {
session_id: SessionID::empty(),
sent_tls13_fake_ccs: false,
dns_name: host_name,
esni,
extra_exts,
}
}
Expand Down
45 changes: 36 additions & 9 deletions rustls/src/client/hs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 ) => (
Expand Down Expand Up @@ -132,9 +133,11 @@ struct InitialState {
}

impl InitialState {
fn new(host_name: webpki::DNSName, extra_exts: Vec<ClientExtension>) -> InitialState {
fn new(host_name: webpki::DNSName,
esni: Option<ESNIHandshakeData>,
extra_exts: Vec<ClientExtension>) -> InitialState {
InitialState {
handshake: HandshakeDetails::new(host_name, extra_exts),
handshake: HandshakeDetails::new(host_name, esni, extra_exts),
}
}

Expand All @@ -149,8 +152,9 @@ impl InitialState {


pub fn start_handshake(sess: &mut ClientSessionImpl, host_name: webpki::DNSName,
esni: Option<ESNIHandshakeData>,
extra_exts: Vec<ClientExtension>) -> NextState {
InitialState::new(host_name, extra_exts)
InitialState::new(host_name, esni, extra_exts)
.emit_initial_client_hello(sess)
}

Expand Down Expand Up @@ -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()));
Expand All @@ -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()));
}
Expand Down
24 changes: 21 additions & 3 deletions rustls/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<dyn verify::ServerCertVerifier>,

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -397,8 +405,8 @@ impl ClientSessionImpl {
}
}

pub fn start_handshake(&mut self, hostname: webpki::DNSName, extra_exts: Vec<ClientExtension>) {
self.state = Some(hs::start_handshake(self, hostname, extra_exts));
pub fn start_handshake(&mut self, hostname: webpki::DNSName, esni: Option<ESNIHandshakeData>, extra_exts: Vec<ClientExtension>) {
self.state = Some(hs::start_handshake(self, hostname, esni, extra_exts));
}

pub fn get_cipher_suites(&self) -> Vec<CipherSuite> {
Expand Down Expand Up @@ -596,7 +604,17 @@ impl ClientSession {
/// hostname of who we want to talk to.
pub fn new(config: &Arc<ClientConfig>, 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<ClientConfig>, hostname: webpki::DNSNameRef, esni: ESNIHandshakeData) -> ClientSession {
let mut imp = ClientSessionImpl::new(config);
imp.start_handshake(hostname.into(), Some(esni), vec![]);
ClientSession { imp }
}

Expand Down
5 changes: 2 additions & 3 deletions rustls/src/client/tls13.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClientExtension>,
hello: &mut ClientHelloDetails,
handshake: &mut HandshakeDetails,
retryreq: Option<&HelloRetryRequest>) {
retryreq: Option<&HelloRetryRequest>) -> Vec<KeyShareEntry> {
// Choose our groups:
// - if we've been asked via HelloRetryRequest for a specific
// one, do that.
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 751fcda

Please sign in to comment.