Skip to content

Commit

Permalink
new extension for TLS cert selection (#32465)
Browse files Browse the repository at this point in the history
fix #30600

Commit Message: Add an extension point to allow overriding TLS
certificate selection behavior.

An extension can select certificate base on the incoming SNI, in both
sync and async mode.

Signed-off-by: doujiang24 <[email protected]>
  • Loading branch information
doujiang24 authored Jul 25, 2024
1 parent 3ab83ee commit b1e3351
Show file tree
Hide file tree
Showing 55 changed files with 1,708 additions and 379 deletions.
9 changes: 8 additions & 1 deletion api/envoy/extensions/transport_sockets/tls/v3/tls.proto
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ message TlsKeyLog {
}

// TLS context shared by both client and server TLS contexts.
// [#next-free-field: 16]
// [#next-free-field: 17]
message CommonTlsContext {
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.CommonTlsContext";

Expand Down Expand Up @@ -274,6 +274,13 @@ message CommonTlsContext {
// [#not-implemented-hide:]
CertificateProviderPluginInstance tls_certificate_provider_instance = 14;

// Custom TLS certificate selector.
//
// Select TLS certificate based on TLS client hello.
// If empty, defaults to native TLS certificate selection behavior:
// DNS SANs or Subject Common Name in TLS certificates is extracted as server name pattern to match SNI.
config.core.v3.TypedExtensionConfig custom_tls_certificate_selector = 16;

// Certificate provider for fetching TLS certificates.
// [#not-implemented-hide:]
CertificateProvider tls_certificate_certificate_provider = 9
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ class UpstreamSSLBaseIntegrationTest : public PostgresBaseIntegrationTest {
NiceMock<Server::Configuration::MockTransportSocketFactoryContext> mock_factory_ctx;
ON_CALL(mock_factory_ctx.server_context_, api()).WillByDefault(testing::ReturnRef(*api_));
auto cfg = *Extensions::TransportSockets::Tls::ServerContextConfigImpl::create(
downstream_tls_context, mock_factory_ctx);
downstream_tls_context, mock_factory_ctx, false);
static auto* client_stats_store = new Stats::TestIsolatedStoreImpl();
Network::DownstreamTransportSocketFactoryPtr tls_context =
Network::DownstreamTransportSocketFactoryPtr{
Expand Down
5 changes: 5 additions & 0 deletions envoy/ssl/context_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ class ServerContextConfig : public virtual ContextConfig {
* @return true if the client cipher preference is enabled, false otherwise.
*/
virtual bool preferClientCiphers() const PURE;

/**
* @return a factory which can be used to create TLS context provider instances.
*/
virtual TlsCertificateSelectorFactory tlsCertificateSelectorFactory() const PURE;
};

using ServerContextConfigPtr = std::unique_ptr<ServerContextConfig>;
Expand Down
3 changes: 0 additions & 3 deletions envoy/ssl/context_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ class CommonFactoryContext;

namespace Ssl {

// Opaque type defined and used by the ``ServerContext``.
struct TlsContext;

using ContextAdditionalInitFunc =
std::function<absl::Status(Ssl::TlsContext& context, const Ssl::TlsCertificateConfig& cert)>;

Expand Down
112 changes: 112 additions & 0 deletions envoy/ssl/handshaker.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,20 @@
#include "openssl/ssl.h"

namespace Envoy {

namespace Server {
namespace Configuration {
class CommonFactoryContext;
} // namespace Configuration
} // namespace Server

namespace Ssl {

// Opaque type defined and used by the ``ServerContext``.
struct TlsContext;

class ServerContextConfig;

class HandshakeCallbacks {
public:
virtual ~HandshakeCallbacks() = default;
Expand Down Expand Up @@ -45,6 +57,12 @@ class HandshakeCallbacks {
* asynchronous.
*/
virtual void onAsynchronousCertValidationComplete() PURE;

/**
* A callback to be called upon certificate selection completion if the selection is
* asynchronous.
*/
virtual void onAsynchronousCertificateSelectionComplete() PURE;
};

/**
Expand Down Expand Up @@ -156,5 +174,99 @@ class HandshakerFactory : public Config::TypedFactory {
virtual SslCtxCb sslctxCb(HandshakerFactoryContext& handshaker_factory_context) const PURE;
};

struct SelectionResult {
enum class SelectionStatus {
// A certificate was successfully selected.
Success,
// Certificate selection will complete asynchronously later.
Pending,
// Certificate selection failed.
Failed,
};
SelectionStatus status; // Status of the certificate selection.
// Selected TLS context which it only be non-null when status is Success.
const Ssl::TlsContext* selected_ctx;
// True if OCSP stapling should be enabled.
bool staple;
};

/**
* Used to return the result from an asynchronous cert selection.
*/
class CertificateSelectionCallback {
public:
virtual ~CertificateSelectionCallback() = default;

virtual Event::Dispatcher& dispatcher() PURE;

/**
* Called when the asynchronous cert selection completes.
* @param selected_ctx selected Ssl::TlsContext, it's empty when selection failed.
* @param staple true when need to set OCSP response.
*/
virtual void onCertificateSelectionResult(OptRef<const Ssl::TlsContext> selected_ctx,
bool staple) PURE;
};

using CertificateSelectionCallbackPtr = std::unique_ptr<CertificateSelectionCallback>;

enum class OcspStapleAction { Staple, NoStaple, Fail, ClientNotCapable };

class TlsCertificateSelector {
public:
virtual ~TlsCertificateSelector() = default;

/**
* Select TLS context based on the client hello in non-QUIC TLS handshake.
*
* @return selected_ctx should only not be null when status is SelectionStatus::Success, and it
* will have the same lifetime as ``ServerContextImpl``.
*/
virtual SelectionResult selectTlsContext(const SSL_CLIENT_HELLO& ssl_client_hello,
CertificateSelectionCallbackPtr cb) PURE;

/**
* Finds the best matching context in QUIC TLS handshake, which doesn't support async mode yet.
*
* @return context will have the same lifetime as ``ServerContextImpl``.
*/
virtual std::pair<const Ssl::TlsContext&, OcspStapleAction>
findTlsContext(absl::string_view sni, bool client_ecdsa_capable, bool client_ocsp_capable,
bool* cert_matched_sni) PURE;
};

using TlsCertificateSelectorPtr = std::unique_ptr<TlsCertificateSelector>;

class TlsCertificateSelectorContext {
public:
virtual ~TlsCertificateSelectorContext() = default;

/**
* @return reference to the initialized Tls Contexts.
*/
virtual const std::vector<TlsContext>& getTlsContexts() const PURE;
};

using TlsCertificateSelectorFactory = std::function<TlsCertificateSelectorPtr(
const ServerContextConfig&, TlsCertificateSelectorContext&)>;

class TlsCertificateSelectorConfigFactory : public Config::TypedFactory {
public:
/**
* @param for_quic true when in quic context, which does not support selecting certificate
* asynchronously.
* @returns a factory to create a TlsCertificateSelector. Accepts the |config| and
* |validation_visitor| for early validation. This virtual base doesn't
* perform MessageUtil::downcastAndValidate, but an implementation should.
*/
virtual TlsCertificateSelectorFactory
createTlsCertificateSelectorFactory(const Protobuf::Message& config,
Server::Configuration::CommonFactoryContext& factory_context,
ProtobufMessage::ValidationVisitor& validation_visitor,
absl::Status& creation_status, bool for_quic) PURE;

std::string category() const override { return "envoy.tls.certificate_selectors"; }
};

} // namespace Ssl
} // namespace Envoy
29 changes: 29 additions & 0 deletions envoy/ssl/ssl_socket_extended_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
namespace Envoy {
namespace Ssl {

// Opaque type defined and used by the ``ServerContext``.
struct TlsContext;

enum class ClientValidationStatus { NotValidated, NoClientCertificate, Validated, Failed };

enum class ValidateStatus {
Expand All @@ -20,6 +23,13 @@ enum class ValidateStatus {
Failed,
};

enum class CertificateSelectionStatus {
NotStarted,
Pending,
Successful,
Failed,
};

/**
* Used to return the result from an asynchronous cert validation.
*/
Expand Down Expand Up @@ -82,6 +92,25 @@ class SslExtendedSocketInfo {
* case of failure.
*/
virtual uint8_t certificateValidationAlert() const PURE;

/**
* @return CertificateSelectionCallbackPtr a callback used to return the cert selection result.
*/
virtual CertificateSelectionCallbackPtr createCertificateSelectionCallback() PURE;

/**
* Called after the cert selection completes either synchronously or asynchronously.
* @param selected_ctx selected Ssl::TlsContext, it's empty when selection failed.
* @param async true if the validation is completed asynchronously.
* @param staple true when need to set OCSP response.
*/
virtual void onCertificateSelectionCompleted(OptRef<const Ssl::TlsContext> selected_ctx,
bool staple, bool async) PURE;

/**
* @return CertificateSelectionStatus the cert selection status.
*/
virtual CertificateSelectionStatus certificateSelectionResult() const PURE;
};

} // namespace Ssl
Expand Down
4 changes: 2 additions & 2 deletions mobile/test/common/integration/test_server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,8 @@ Network::DownstreamTransportSocketFactoryPtr TestServer::createUpstreamTlsContex
ctx->mutable_trusted_ca()->set_filename(
TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem"));
tls_context.mutable_common_tls_context()->add_alpn_protocols("h2");
auto cfg = *Extensions::TransportSockets::Tls::ServerContextConfigImpl::create(tls_context,
factory_context);
auto cfg = *Extensions::TransportSockets::Tls::ServerContextConfigImpl::create(
tls_context, factory_context, false);
static auto* upstream_stats_store = new Stats::TestIsolatedStoreImpl();
return *Extensions::TransportSockets::Tls::ServerSslSocketFactory::create(
std::move(cfg), context_manager_, *upstream_stats_store->rootScope(),
Expand Down
1 change: 1 addition & 0 deletions mobile/test/performance/files_em_does_not_use
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ source/common/router/vhds.h
source/common/tls/server_context_config_impl.h
source/common/tls/server_context_impl.h
source/common/tls/ocsp/ocsp.h
source/common/tls/default_tls_certificate_selector.h
2 changes: 1 addition & 1 deletion source/common/quic/quic_server_transport_socket_factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ QuicServerTransportSocketConfigFactory::createTransportSocketFactory(
config, context.messageValidationVisitor());
absl::StatusOr<std::unique_ptr<Extensions::TransportSockets::Tls::ServerContextConfigImpl>>
server_config_or_error = Extensions::TransportSockets::Tls::ServerContextConfigImpl::create(
quic_transport.downstream_tls_context(), context);
quic_transport.downstream_tls_context(), context, true);
RETURN_IF_NOT_OK(server_config_or_error.status());
auto server_config = std::move(server_config_or_error.value());
// TODO(RyanTheOptimist): support TLS client authentication.
Expand Down
3 changes: 3 additions & 0 deletions source/common/tls/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ envoy_cc_library(
hdrs = ["server_context_config_impl.h"],
deps = [
":context_config_lib",
":server_context_lib",
"@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto",
],
)
Expand Down Expand Up @@ -222,9 +223,11 @@ envoy_cc_library(
envoy_cc_library(
name = "server_context_lib",
srcs = [
"default_tls_certificate_selector.cc",
"server_context_impl.cc",
],
hdrs = [
"default_tls_certificate_selector.h",
"server_context_impl.h",
],
deps = [
Expand Down
1 change: 1 addition & 0 deletions source/common/tls/context_config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "source/common/common/empty_string.h"
#include "source/common/config/datasource.h"
#include "source/common/network/cidr_range.h"
#include "source/common/protobuf/message_validator_impl.h"
#include "source/common/protobuf/utility.h"
#include "source/common/secret/sds_api.h"
#include "source/common/ssl/certificate_validation_context_config_impl.h"
Expand Down
2 changes: 1 addition & 1 deletion source/common/tls/context_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ ValidationResults ContextImpl::customVerifyCertChainForQuic(

namespace Ssl {

bool TlsContext::isCipherEnabled(uint16_t cipher_id, uint16_t client_version) {
bool TlsContext::isCipherEnabled(uint16_t cipher_id, uint16_t client_version) const {
const SSL_CIPHER* c = SSL_get_cipher_by_value(cipher_id);
if (c == nullptr) {
return false;
Expand Down
2 changes: 1 addition & 1 deletion source/common/tls/context_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ struct TlsContext {
#endif

std::string getCertChainFileName() const { return cert_chain_file_path_; };
bool isCipherEnabled(uint16_t cipher_id, uint16_t client_version);
bool isCipherEnabled(uint16_t cipher_id, uint16_t client_version) const;
Envoy::Ssl::PrivateKeyMethodProviderSharedPtr getPrivateKeyMethodProvider() {
return private_key_method_provider_;
}
Expand Down
Loading

0 comments on commit b1e3351

Please sign in to comment.