Support stateful resumption in TLS1.3

Prior to this we only supported ticket-style resumption.
This commit is contained in:
Joseph Birr-Pixton 2018-09-25 21:37:33 +01:00 committed by ctz
parent 1b3e966cf6
commit e0b9be1970
7 changed files with 234 additions and 66 deletions

View File

@ -20,6 +20,8 @@ Rustls is currently in development and hence unstable. [Here's what I'm working
- Fix a bug in rustls::Stream for non-blocking transports.
- Move TLS1.3 support from draft 28 to final RFC8446 version.
- Don't offer (eg) TLS1.3 if no TLS1.3 suites are configured.
- Support stateful resumption in TLS1.3. Stateless resumption
was previously supported, but is not the default configuration.
* 0.13.1 (2018-08-17):
- Fix a bug in rustls::Stream for non-blocking transports
(backport).

View File

@ -297,6 +297,8 @@ fn make_server_cfg(opts: &Options) -> Arc<rustls::ServerConfig> {
if opts.tickets {
cfg.ticketer = rustls::Ticketer::new();
} else if opts.resumes == 0 {
cfg.set_persistence(Arc::new(rustls::NoServerSessionStorage {}));
}
if !opts.protocols.is_empty() {

View File

@ -1009,7 +1009,8 @@ impl State for ExpectTLS13EncryptedExtensions {
}
if self.handshake.resuming_session.is_some() {
if sess.common.early_traffic {
let was_early_traffic = sess.common.early_traffic;
if was_early_traffic {
if exts.early_data_extension_offered() {
sess.early_data.accepted();
} else {
@ -1018,7 +1019,7 @@ impl State for ExpectTLS13EncryptedExtensions {
}
}
if !sess.common.early_traffic {
if was_early_traffic && !sess.common.early_traffic {
// If no early traffic, set the encryption key for handshakes
let suite = sess.common.get_suite_assert();
let write_key = sess.common.get_key_schedule()

View File

@ -1,6 +1,4 @@
use msgs::enums::SignatureScheme;
use msgs::handshake::SessionID;
use rand;
use sign;
use key;
use webpki;
@ -14,15 +12,15 @@ use std::sync::{Arc, Mutex};
pub struct NoServerSessionStorage {}
impl server::StoresServerSessions for NoServerSessionStorage {
fn generate(&self) -> SessionID {
SessionID::empty()
}
fn put(&self, _id: Vec<u8>, _sec: Vec<u8>) -> bool {
false
}
fn get(&self, _id: &[u8]) -> Option<Vec<u8>> {
None
}
fn take(&self, _id: &[u8]) -> Option<Vec<u8>> {
None
}
}
/// An implementor of `StoresServerSessions` that stores everything
@ -54,12 +52,6 @@ impl ServerSessionMemoryCache {
}
impl server::StoresServerSessions for ServerSessionMemoryCache {
fn generate(&self) -> SessionID {
let mut v = [0u8; 32];
rand::fill_random(&mut v);
SessionID::new(&v)
}
fn put(&self, key: Vec<u8>, value: Vec<u8>) -> bool {
self.cache.lock()
.unwrap()
@ -73,6 +65,12 @@ impl server::StoresServerSessions for ServerSessionMemoryCache {
.unwrap()
.get(key).cloned()
}
fn take(&self, key: &[u8]) -> Option<Vec<u8>> {
self.cache.lock()
.unwrap()
.remove(key)
}
}
/// Something which never produces tickets.
@ -193,14 +191,6 @@ mod test {
use super::*;
use StoresServerSessions;
#[test]
fn test_noserversessionstorage_yields_no_sessid() {
let c = NoServerSessionStorage {};
assert_eq!(c.generate(), SessionID::empty());
assert_eq!(c.generate().len(), 0);
assert!(c.generate().is_empty());
}
#[test]
fn test_noserversessionstorage_drops_put() {
let c = NoServerSessionStorage {};
@ -216,12 +206,6 @@ mod test {
assert_eq!(c.get(&[0x02]), None);
}
#[test]
fn test_serversessionmemorycache_yields_sessid() {
let c = ServerSessionMemoryCache::new(4);
assert_eq!(c.generate().len(), 32);
}
#[test]
fn test_serversessionmemorycache_accepts_put() {
let c = ServerSessionMemoryCache::new(4);

View File

@ -768,6 +768,22 @@ impl ExpectClientHello {
sess.common.send_msg(m, false);
}
fn attempt_tls13_ticket_decryption(&mut self,
sess: &mut ServerSessionImpl,
ticket: &[u8]) -> Option<persist::ServerSessionValue> {
if sess.config.ticketer.enabled() {
sess.config
.ticketer
.decrypt(ticket)
.and_then(|plain| persist::ServerSessionValue::read_bytes(&plain))
} else {
sess.config
.session_storage
.take(ticket)
.and_then(|plain| persist::ServerSessionValue::read_bytes(&plain))
}
}
fn start_resumption(mut self,
sess: &mut ServerSessionImpl,
client_hello: &ClientHelloPayload,
@ -881,10 +897,7 @@ impl ExpectClientHello {
}
for (i, psk_id) in psk_offer.identities.iter().enumerate() {
let maybe_resume = sess.config
.ticketer
.decrypt(&psk_id.identity.0)
.and_then(|plain| persist::ServerSessionValue::read_bytes(&plain));
let maybe_resume = self.attempt_tls13_ticket_decryption(sess, &psk_id.identity.0);
if !can_resume(sess, &self.handshake, &maybe_resume) {
continue;
@ -1129,10 +1142,9 @@ impl State for ExpectClientHello {
// If we're not offered a ticket or a potential session ID,
// allocate a session ID.
if self.handshake.session_id.is_empty() && !ticket_received {
let sessid = sess.config
.session_storage
.generate();
self.handshake.session_id = sessid;
let mut bytes = [0u8; 32];
rand::fill_random(&mut bytes);
self.handshake.session_id = SessionID::new(&bytes);
}
// Perhaps resume? If we received a ticket, the sessionid
@ -1672,11 +1684,8 @@ impl ExpectTLS13Finished {
})
}
fn emit_ticket_tls13(&mut self, sess: &mut ServerSessionImpl) {
if !self.send_ticket {
return;
}
fn emit_stateless_ticket_tls13(&mut self, sess: &mut ServerSessionImpl) {
debug_assert!(self.send_ticket);
let nonce = rand::random_vec(32);
let plain = get_server_session_value_tls13(&self.handshake, sess, &nonce)
.get_encoding();
@ -1701,10 +1710,38 @@ impl ExpectTLS13Finished {
}),
};
trace!("sending new ticket {:?}", m);
trace!("sending new stateless ticket {:?}", m);
self.handshake.transcript.add_message(&m);
sess.common.send_msg(m, true);
}
fn emit_stateful_ticket_tls13(&mut self, sess: &mut ServerSessionImpl) {
debug_assert!(self.send_ticket);
let nonce = rand::random_vec(32);
let id = rand::random_vec(32);
let plain = get_server_session_value_tls13(&self.handshake, sess, &nonce)
.get_encoding();
if sess.config.session_storage.put(id.clone(), plain) {
let stateful_lifetime = 24 * 60 * 60; // this is a bit of a punt
let age_add = rand::random_u32();
let payload = NewSessionTicketPayloadTLS13::new(stateful_lifetime, age_add, nonce, id);
let m = Message {
typ: ContentType::Handshake,
version: ProtocolVersion::TLSv1_3,
payload: MessagePayload::Handshake(HandshakeMessagePayload {
typ: HandshakeType::NewSessionTicket,
payload: HandshakePayload::NewSessionTicketTLS13(payload),
}),
};
trace!("sending new stateful ticket {:?}", m);
self.handshake.transcript.add_message(&m);
sess.common.send_msg(m, true);
} else {
trace!("resumption not available; not issuing ticket");
}
}
}
impl State for ExpectTLS13Finished {
@ -1749,8 +1786,12 @@ impl State for ExpectTLS13Finished {
.get_mut_key_schedule()
.current_client_traffic_secret = read_key;
if sess.config.ticketer.enabled() {
self.emit_ticket_tls13(sess);
if self.send_ticket {
if sess.config.ticketer.enabled() {
self.emit_stateless_ticket_tls13(sess);
} else {
self.emit_stateful_ticket_tls13(sess);
}
}
sess.common.we_now_encrypting();

View File

@ -3,7 +3,7 @@ use keylog::{KeyLog, NoKeyLog};
use suites::{SupportedCipherSuite, ALL_CIPHERSUITES};
use msgs::enums::{ContentType, SignatureScheme};
use msgs::enums::{AlertDescription, HandshakeType, ProtocolVersion};
use msgs::handshake::{ServerExtension, SessionID};
use msgs::handshake::ServerExtension;
use msgs::message::Message;
use error::TLSError;
use sign;
@ -21,29 +21,37 @@ mod hs;
mod common;
pub mod handy;
/// A trait for the ability to generate Session IDs, and store
/// server session data. The keys and values are opaque.
/// A trait for the ability to store server session data.
///
/// The keys and values are opaque.
///
/// Both the keys and values should be treated as
/// **highly sensitive data**, containing enough key material
/// to break all security of the corresponding session.
/// to break all security of the corresponding sessions.
///
/// `put` is a mutating operation; this isn't expressed
/// Implementations can be lossy (in other words, forgetting
/// key/value pairs) without any negative security consequences.
///
/// However, note that `take` **must** reliably delete a returned
/// value. If it does not, there may be security consequences.
///
/// `put` and `take` are mutating operations; this isn't expressed
/// in the type system to allow implementations freedom in
/// how to achieve interior mutability. `Mutex` is a common
/// choice.
pub trait StoresServerSessions : Send + Sync {
/// Generate a session ID.
fn generate(&self) -> SessionID;
/// Store session secrets encoded in `value` against key `id`,
/// overwrites any existing value against `id`. Returns `true`
/// Store session secrets encoded in `value` against `key`,
/// overwrites any existing value against `key`. Returns `true`
/// if the value was stored.
fn put(&self, key: Vec<u8>, value: Vec<u8>) -> bool;
/// Find a session with the given `id`. Return it, or None
/// Find a value with the given `key`. Return it, or None
/// if it doesn't exist.
fn get(&self, key: &[u8]) -> Option<Vec<u8>>;
/// Find a value with the given `key`. Return it and delete it;
/// or None if it doesn't exist.
fn take(&self, key: &[u8]) -> Option<Vec<u8>>;
}
/// A trait for the ability to encrypt and decrypt tickets.

View File

@ -1,9 +1,10 @@
// Assorted public API tests.
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::fs;
use std::mem;
use std::fmt;
use std::io::{self, Write, Read};
extern crate rustls;
@ -23,13 +24,15 @@ use rustls::KeyLog;
extern crate webpki;
fn transfer(left: &mut Session, right: &mut Session) {
fn transfer(left: &mut Session, right: &mut Session) -> usize {
let mut buf = [0u8; 262144];
let mut total = 0;
while left.wants_write() {
let sz = left.write_tls(&mut buf.as_mut()).unwrap();
total += sz;
if sz == 0 {
return;
return total;
}
let mut offs = 0;
@ -40,6 +43,8 @@ fn transfer(left: &mut Session, right: &mut Session) {
}
}
}
total
}
#[derive(Clone, Copy)]
@ -139,13 +144,15 @@ fn make_pair_for_arc_configs(client_config: &Arc<ClientConfig>,
)
}
fn do_handshake(client: &mut ClientSession, server: &mut ServerSession) {
fn do_handshake(client: &mut ClientSession, server: &mut ServerSession) -> (usize, usize) {
let (mut to_client, mut to_server) = (0, 0);
while server.is_handshaking() || client.is_handshaking() {
transfer(client, server);
to_server += transfer(client, server);
server.process_new_packets().unwrap();
transfer(server, client);
to_client += transfer(server, client);
client.process_new_packets().unwrap();
}
(to_server, to_client)
}
struct AllClientVersions {
@ -587,14 +594,14 @@ fn client_checks_server_certificate_with_given_name() {
}
struct ClientCheckCertResolve {
query_count: atomic::AtomicUsize,
query_count: AtomicUsize,
expect_queries: usize
}
impl ClientCheckCertResolve {
fn new(expect_queries: usize) -> ClientCheckCertResolve {
ClientCheckCertResolve {
query_count: atomic::AtomicUsize::new(0),
query_count: AtomicUsize::new(0),
expect_queries: expect_queries
}
}
@ -602,7 +609,7 @@ impl ClientCheckCertResolve {
impl Drop for ClientCheckCertResolve {
fn drop(&mut self) {
let count = self.query_count.load(atomic::Ordering::SeqCst);
let count = self.query_count.load(Ordering::SeqCst);
assert_eq!(count, self.expect_queries);
}
}
@ -612,7 +619,7 @@ impl ResolvesClientCert for ClientCheckCertResolve {
acceptable_issuers: &[&[u8]],
sigschemes: &[SignatureScheme])
-> Option<sign::CertifiedKey> {
self.query_count.fetch_add(1, atomic::Ordering::SeqCst);
self.query_count.fetch_add(1, Ordering::SeqCst);
if acceptable_issuers.len() == 0 {
panic!("no issuers offered by server");
@ -1683,8 +1690,8 @@ fn vectored_write_for_server_handshake() {
{
let mut pipe = OtherSession::new(&mut client);
let wrlen = server.writev_tls(&mut pipe).unwrap();
assert_eq!(wrlen, 74);
assert_eq!(pipe.writevs, vec![vec![42, 32]]);
assert_eq!(wrlen, 177);
assert_eq!(pipe.writevs, vec![vec![103, 42, 32]]);
}
assert_eq!(server.is_handshaking(), false);
@ -1746,3 +1753,126 @@ fn vectored_write_with_slow_client() {
}
check_read(&mut client, b"01234567890123456789");
}
struct ServerStorage {
storage: Arc<rustls::StoresServerSessions>,
put_count: AtomicUsize,
get_count: AtomicUsize,
take_count: AtomicUsize,
}
impl ServerStorage {
fn new() -> ServerStorage {
ServerStorage {
storage: rustls::ServerSessionMemoryCache::new(1024),
put_count: AtomicUsize::new(0),
get_count: AtomicUsize::new(0),
take_count: AtomicUsize::new(0),
}
}
fn puts(&self) -> usize { self.put_count.load(Ordering::SeqCst) }
fn gets(&self) -> usize { self.get_count.load(Ordering::SeqCst) }
fn takes(&self) -> usize { self.take_count.load(Ordering::SeqCst) }
}
impl fmt::Debug for ServerStorage {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "(put: {:?}, get: {:?}, take: {:?})",
self.put_count, self.get_count, self.take_count)
}
}
impl rustls::StoresServerSessions for ServerStorage {
fn put(&self, key: Vec<u8>, value: Vec<u8>) -> bool {
self.put_count.fetch_add(1, Ordering::SeqCst);
self.storage.put(key, value)
}
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
self.get_count.fetch_add(1, Ordering::SeqCst);
self.storage.get(key)
}
fn take(&self, key: &[u8]) -> Option<Vec<u8>> {
self.take_count.fetch_add(1, Ordering::SeqCst);
self.storage.take(key)
}
}
#[test]
fn tls13_stateful_resumption() {
let kt = KeyType::RSA;
let mut client_config = make_client_config(kt);
client_config.versions = vec![ ProtocolVersion::TLSv1_3 ];
let client_config = Arc::new(client_config);
let mut server_config = make_server_config(kt);
let storage = Arc::new(ServerStorage::new());
server_config.session_storage = storage.clone();
let server_config = Arc::new(server_config);
// full handshake
let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config);
let (full_c2s, full_s2c) = do_handshake(&mut client, &mut server);
assert_eq!(storage.puts(), 1);
assert_eq!(storage.gets(), 0);
assert_eq!(storage.takes(), 0);
// resumed
let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config);
let (resume_c2s, resume_s2c) = do_handshake(&mut client, &mut server);
assert!(resume_c2s > full_c2s);
assert!(resume_s2c < full_s2c);
assert_eq!(storage.puts(), 2);
assert_eq!(storage.gets(), 0);
assert_eq!(storage.takes(), 1);
// resumed again
let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config);
let (resume2_c2s, resume2_s2c) = do_handshake(&mut client, &mut server);
assert_eq!(resume_s2c, resume2_s2c);
assert_eq!(resume_c2s, resume2_c2s);
assert_eq!(storage.puts(), 3);
assert_eq!(storage.gets(), 0);
assert_eq!(storage.takes(), 2);
}
#[test]
fn tls13_stateless_resumption() {
let kt = KeyType::RSA;
let mut client_config = make_client_config(kt);
client_config.versions = vec![ ProtocolVersion::TLSv1_3 ];
let client_config = Arc::new(client_config);
let mut server_config = make_server_config(kt);
server_config.ticketer = rustls::Ticketer::new();
let storage = Arc::new(ServerStorage::new());
server_config.session_storage = storage.clone();
let server_config = Arc::new(server_config);
// full handshake
let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config);
let (full_c2s, full_s2c) = do_handshake(&mut client, &mut server);
assert_eq!(storage.puts(), 0);
assert_eq!(storage.gets(), 0);
assert_eq!(storage.takes(), 0);
// resumed
let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config);
let (resume_c2s, resume_s2c) = do_handshake(&mut client, &mut server);
assert!(resume_c2s > full_c2s);
assert!(resume_s2c < full_s2c);
assert_eq!(storage.puts(), 0);
assert_eq!(storage.gets(), 0);
assert_eq!(storage.takes(), 0);
// resumed again
let (mut client, mut server) = make_pair_for_arc_configs(&client_config, &server_config);
let (resume2_c2s, resume2_s2c) = do_handshake(&mut client, &mut server);
assert_eq!(resume_s2c, resume2_s2c);
assert_eq!(resume_c2s, resume2_c2s);
assert_eq!(storage.puts(), 0);
assert_eq!(storage.gets(), 0);
assert_eq!(storage.takes(), 0);
}