Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense (in the future) to have a corresponding server-side setting for how many session tickets a TLS 1.3 server will issue at a time? the RFC says:

Servers that issue tickets SHOULD offer at least as many tickets as the number of connections that a client might use; for example, a web browser using HTTP/1.1 [RFC7230] might open six connections to a server.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but it's currently hardcoded to static const int kNumTickets = 2 in BoringSSL (see: https://boringssl.googlesource.com/boringssl/+/c0c9001440db8121bdc1ff1307b3a9aedf26fcd8/ssl/tls13_server.cc#165). cc @davidben

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Should this feature be disabled or enabled by default?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any security implications from enabling by default? E.g. are we materially increasing the amount of code that might be subject to compromise in BoringSSL etc. I have zero clue on this, but my inclination would be if there was a tradeoff to sacrifice performance (i.e. the resumption) for improved default security posture.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a case of a "privacy leak" (passive observer being able to correlate connections from the same user by looking at the session that's being resumed, which is sent unencrypted in ClientHello) in TLS versions older than 1.3, but that's mostly a threat to end-users (so also a single-user client-side proxy) and not middle/edge proxies and/or service mesh, so I don't think that it justifies having this off by default in Envoy.

Note: TLSv1.3 sends single-use sessions, so the default of 1 is probably too small. Perhaps we could make it vary by default, i.e. 1 if tls_maximum_protocol_version is smaller than TLSv1.3, and 4(?) otherwise.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the common case is service mesh and middle/edge proxies, so we should optimize for that. It's probably not great to be optimizing for TLS 1.3 quiet yet, I don't think this is universal by far.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I somehow missed this comment earlier.

What I meant regarding TLS v1.3 is basically:

if upstream_tls_context.tls_params.tls_maximum_protocol_version == TLSv1_3:
   max_session_keys = 4;
else
   max_session_keys = 1;

But I'm fine leaving the default at 1 for the time being, and we can revisit it later, when we enable TLSv1.3 by default.

}

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check or ASSERT errors for these BoringSSL calls?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for those. SSL_CTX_set_session_cache_mode() cannot fail and returns previously configured mode, SSL_CTX_sess_set_new_cb() cannot fail and returns void.

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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are safe for multi cert work, given that the client contexts will continue to have a single cert, but could you comment here on whether in the future, if we support multiple client certs, whether anything needs to change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that we'll ever support multiple client certificates that can affect sessions, since client certificates are not revalidated during session resumption by the server.

In any case, this would be covered by #5073.

// 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