Skip to content

Commit

Permalink
tls: add support for client-side session resumption. (envoyproxy#4791)
Browse files Browse the repository at this point in the history
Risk Level: Low
Testing: bazel test //test/...

Signed-off-by: Piotr Sikora <piotrsikora@google.com>
Signed-off-by: Fred Douglas <fredlas@google.com>
  • Loading branch information
PiotrSikora authored and fredlas committed Mar 5, 2019
1 parent b7ec6b1 commit 6ee9d92
Show file tree
Hide file tree
Showing 8 changed files with 367 additions and 30 deletions.
6 changes: 6 additions & 0 deletions api/envoy/api/v2/auth/cert.proto
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,12 @@ message UpstreamTlsContext {
//
// TLS renegotiation is considered insecure and shouldn't be used unless absolutely necessary.
bool allow_renegotiation = 3;

// Maximum number of session keys (Pre-Shared Keys for TLSv1.3+, Session IDs and Session Tickets
// for TLSv1.2 and older) to store for the purpose of session resumption.
//
// Defaults to 1, setting this to 0 disables session resumption.
google.protobuf.UInt32Value max_session_keys = 4;
}

message DownstreamTlsContext {
Expand Down
5 changes: 5 additions & 0 deletions include/envoy/ssl/context_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ class ClientContextConfig : public virtual ContextConfig {
* @return true if server-initiated TLS renegotiation will be allowed.
*/
virtual bool allowRenegotiation() const PURE;

/**
* @return The maximum number of session keys to store.
*/
virtual size_t maxSessionKeys() const PURE;
};

typedef std::unique_ptr<ClientContextConfig> ClientContextConfigPtr;
Expand Down
5 changes: 4 additions & 1 deletion source/common/ssl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ envoy_cc_library(
"context_impl.h",
"context_manager_impl.h",
],
external_deps = ["ssl"],
external_deps = [
"abseil_synchronization",
"ssl",
],
deps = [
":utility_lib",
"//include/envoy/ssl:context_config_interface",
Expand Down
3 changes: 2 additions & 1 deletion source/common/ssl/context_config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ ClientContextConfigImpl::ClientContextConfigImpl(
const envoy::api::v2::auth::UpstreamTlsContext& config,
Server::Configuration::TransportSocketFactoryContext& factory_context)
: ContextConfigImpl(config.common_tls_context(), factory_context),
server_name_indication_(config.sni()), allow_renegotiation_(config.allow_renegotiation()) {
server_name_indication_(config.sni()), allow_renegotiation_(config.allow_renegotiation()),
max_session_keys_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_session_keys, 1)) {
// BoringSSL treats this as a C string, so embedded NULL characters will not
// be handled correctly.
if (server_name_indication_.find('\0') != std::string::npos) {
Expand Down
2 changes: 2 additions & 0 deletions source/common/ssl/context_config_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,12 @@ class ClientContextConfigImpl : public ContextConfigImpl, public ClientContextCo
// Ssl::ClientContextConfig
const std::string& serverNameIndication() const override { return server_name_indication_; }
bool allowRenegotiation() const override { return allow_renegotiation_; }
size_t maxSessionKeys() const override { return max_session_keys_; }

private:
const std::string server_name_indication_;
const bool allow_renegotiation_;
const size_t max_session_keys_;
};

class ServerContextConfigImpl : public ContextConfigImpl, public ServerContextConfig {
Expand Down
61 changes: 57 additions & 4 deletions source/common/ssl/context_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ std::vector<uint8_t> ContextImpl::parseAlpnProtocols(const std::string& alpn_pro
return out;
}

bssl::UniquePtr<SSL> ContextImpl::newSsl(absl::optional<std::string>) const {
bssl::UniquePtr<SSL> ContextImpl::newSsl(absl::optional<std::string>) {
return bssl::UniquePtr<SSL>(SSL_new(ctx_.get()));
}

Expand Down Expand Up @@ -490,16 +490,27 @@ ClientContextImpl::ClientContextImpl(Stats::Scope& scope, const ClientContextCon
TimeSource& time_source)
: ContextImpl(scope, config, time_source),
server_name_indication_(config.serverNameIndication()),
allow_renegotiation_(config.allowRenegotiation()) {
allow_renegotiation_(config.allowRenegotiation()),
max_session_keys_(config.maxSessionKeys()) {
if (!parsed_alpn_protocols_.empty()) {
int rc = SSL_CTX_set_alpn_protos(ctx_.get(), &parsed_alpn_protocols_[0],
parsed_alpn_protocols_.size());
RELEASE_ASSERT(rc == 0, "");
}

if (max_session_keys_ > 0) {
SSL_CTX_set_session_cache_mode(ctx_.get(), SSL_SESS_CACHE_CLIENT);
SSL_CTX_sess_set_new_cb(ctx_.get(), [](SSL* ssl, SSL_SESSION* session) -> int {
ContextImpl* context_impl =
static_cast<ContextImpl*>(SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl), sslContextIndex()));
ClientContextImpl* client_context_impl = dynamic_cast<ClientContextImpl*>(context_impl);
RELEASE_ASSERT(client_context_impl != nullptr, ""); // for Coverity
return client_context_impl->newSessionKey(session);
});
}
}

bssl::UniquePtr<SSL>
ClientContextImpl::newSsl(absl::optional<std::string> override_server_name) const {
bssl::UniquePtr<SSL> ClientContextImpl::newSsl(absl::optional<std::string> override_server_name) {
bssl::UniquePtr<SSL> ssl_con(ContextImpl::newSsl(absl::nullopt));

std::string server_name_indication =
Expand All @@ -514,9 +525,51 @@ ClientContextImpl::newSsl(absl::optional<std::string> override_server_name) cons
SSL_set_renegotiate_mode(ssl_con.get(), ssl_renegotiate_freely);
}

if (max_session_keys_ > 0) {
if (session_keys_single_use_) {
// Stored single-use session keys, use write/write locks.
absl::WriterMutexLock l(&session_keys_mu_);
if (!session_keys_.empty()) {
// Use the most recently stored session key, since it has the highest
// probability of still being recognized/accepted by the server.
SSL_SESSION* session = session_keys_.front().get();
SSL_set_session(ssl_con.get(), session);
// Remove single-use session key (TLS 1.3) after first use.
if (SSL_SESSION_should_be_single_use(session)) {
session_keys_.pop_front();
}
}
} else {
// Never stored single-use session keys, use read/write locks.
absl::ReaderMutexLock l(&session_keys_mu_);
if (!session_keys_.empty()) {
// Use the most recently stored session key, since it has the highest
// probability of still being recognized/accepted by the server.
SSL_SESSION* session = session_keys_.front().get();
SSL_set_session(ssl_con.get(), session);
}
}
}

return ssl_con;
}

int ClientContextImpl::newSessionKey(SSL_SESSION* session) {
// In case we ever store single-use session key (TLS 1.3),
// we need to switch to using write/write locks.
if (SSL_SESSION_should_be_single_use(session)) {
session_keys_single_use_ = true;
}
absl::WriterMutexLock l(&session_keys_mu_);
// Evict oldest entries.
while (session_keys_.size() >= max_session_keys_) {
session_keys_.pop_back();
}
// Add new session key at the front of the queue, so that it's used first.
session_keys_.push_front(bssl::UniquePtr<SSL_SESSION>(session));
return 1; // Tell BoringSSL that we took ownership of the session.
}

ServerContextImpl::ServerContextImpl(Stats::Scope& scope, const ServerContextConfig& config,
const std::vector<std::string>& server_names,
TimeSource& time_source)
Expand Down
12 changes: 10 additions & 2 deletions source/common/ssl/context_impl.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <deque>
#include <functional>
#include <string>
#include <vector>
Expand All @@ -11,6 +12,7 @@

#include "common/ssl/context_manager_impl.h"

#include "absl/synchronization/mutex.h"
#include "absl/types/optional.h"
#include "openssl/ssl.h"

Expand Down Expand Up @@ -42,7 +44,7 @@ struct SslStats {

class ContextImpl : public virtual Context {
public:
virtual bssl::UniquePtr<SSL> newSsl(absl::optional<std::string> override_server_name) const;
virtual bssl::UniquePtr<SSL> newSsl(absl::optional<std::string> override_server_name);

/**
* Logs successful TLS handshake and updates stats.
Expand Down Expand Up @@ -143,11 +145,17 @@ class ClientContextImpl : public ContextImpl, public ClientContext {
ClientContextImpl(Stats::Scope& scope, const ClientContextConfig& config,
TimeSource& time_source);

bssl::UniquePtr<SSL> newSsl(absl::optional<std::string> override_server_name) const override;
bssl::UniquePtr<SSL> newSsl(absl::optional<std::string> override_server_name) override;

private:
int newSessionKey(SSL_SESSION* session);

const std::string server_name_indication_;
const bool allow_renegotiation_;
const size_t max_session_keys_;
absl::Mutex session_keys_mu_;
std::deque<bssl::UniquePtr<SSL_SESSION>> session_keys_ GUARDED_BY(session_keys_mu_);
bool session_keys_single_use_{false};
};

class ServerContextImpl : public ContextImpl, public ServerContext {
Expand Down
Loading

0 comments on commit 6ee9d92

Please sign in to comment.