bound session cache
When establishing new TLS sessions, servers may send multiple session tickets (RFC8446 4.6.1). hyper-boring caches tickets without placing a limit on how many tickets are cached. This leads to unbounded growth of hyper-boring's cache and leaves clients vulnerable to malicious servers who might send many session tickets to exhaust a client's available memory. This change bounds the cache to a default of 8 tickets.
This commit is contained in:
parent
3d9a5e3244
commit
8db6134c75
|
|
@ -40,24 +40,32 @@ impl Borrow<[u8]> for HashSession {
|
||||||
pub struct SessionCache {
|
pub struct SessionCache {
|
||||||
sessions: HashMap<SessionKey, LinkedHashSet<HashSession>>,
|
sessions: HashMap<SessionKey, LinkedHashSet<HashSession>>,
|
||||||
reverse: HashMap<HashSession, SessionKey>,
|
reverse: HashMap<HashSession, SessionKey>,
|
||||||
|
/// Maximum capacity of LinkedHashSet per SessionKey
|
||||||
|
per_key_session_capacity: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionCache {
|
impl SessionCache {
|
||||||
pub fn new() -> SessionCache {
|
pub fn with_capacity(per_key_session_capacity: usize) -> SessionCache {
|
||||||
SessionCache {
|
SessionCache {
|
||||||
sessions: HashMap::new(),
|
sessions: HashMap::new(),
|
||||||
reverse: HashMap::new(),
|
reverse: HashMap::new(),
|
||||||
|
per_key_session_capacity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, key: SessionKey, session: SslSession) {
|
pub fn insert(&mut self, key: SessionKey, session: SslSession) {
|
||||||
let session = HashSession(session);
|
let session = HashSession(session);
|
||||||
|
|
||||||
self.sessions
|
let sessions = self.sessions.entry(key.clone()).or_default();
|
||||||
.entry(key.clone())
|
|
||||||
.or_default()
|
|
||||||
.insert(session.clone());
|
|
||||||
|
|
||||||
|
// if sessions exceed capacity, discard oldest
|
||||||
|
if sessions.len() >= self.per_key_session_capacity {
|
||||||
|
if let Some(hash) = sessions.pop_front() {
|
||||||
|
self.reverse.remove(&hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sessions.insert(session.clone());
|
||||||
self.reverse.insert(session, key);
|
self.reverse.insert(session, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,8 +102,19 @@ impl HttpsLayer {
|
||||||
/// Creates a new `HttpsLayer`.
|
/// Creates a new `HttpsLayer`.
|
||||||
///
|
///
|
||||||
/// The session cache configuration of `ssl` will be overwritten.
|
/// The session cache configuration of `ssl` will be overwritten.
|
||||||
pub fn with_connector(mut ssl: SslConnectorBuilder) -> Result<HttpsLayer, ErrorStack> {
|
pub fn with_connector(ssl: SslConnectorBuilder) -> Result<HttpsLayer, ErrorStack> {
|
||||||
let cache = Arc::new(Mutex::new(SessionCache::new()));
|
Self::with_connector_and_capacity(ssl, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `HttpsLayer` with session capacity.
|
||||||
|
///
|
||||||
|
/// The session cache configuration of `ssl` will be overwritten. Session capacity is per
|
||||||
|
/// session key (domain).
|
||||||
|
pub fn with_connector_and_capacity(
|
||||||
|
mut ssl: SslConnectorBuilder,
|
||||||
|
capacity: usize,
|
||||||
|
) -> Result<HttpsLayer, ErrorStack> {
|
||||||
|
let cache = Arc::new(Mutex::new(SessionCache::with_capacity(capacity)));
|
||||||
|
|
||||||
ssl.set_session_cache_mode(SslSessionCacheMode::CLIENT);
|
ssl.set_session_cache_mode(SslSessionCacheMode::CLIENT);
|
||||||
|
|
||||||
|
|
@ -116,11 +127,6 @@ impl HttpsLayer {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ssl.set_remove_session_callback({
|
|
||||||
let cache = cache.clone();
|
|
||||||
move |_, session| cache.lock().remove(session)
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(HttpsLayer {
|
Ok(HttpsLayer {
|
||||||
inner: Inner {
|
inner: Inner {
|
||||||
ssl: ssl.build(),
|
ssl: ssl.build(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue