Skip to content

Commit

Permalink
tls: support BoringSSL private key async functionality (#6326)
Browse files Browse the repository at this point in the history
This PR adds BoringSSL private key API abstraction, as discussed in #6248. All comments and discussion is welcomed to get the API sufficient for most private key API tasks.

The PR contains the proposed API and the way how it can be used from ssl_socket.h. Also there is some code showing how the PrivateKeyMethodProvider is coming from TLS certificate config. Two example private key method providers are included in the tests.

Description: tls: support BoringSSL private key async functionality
Risk Level: medium
Testing: two basic private key provider implementation
Docs Changes: TLS arch doc, cert.proto doc

Signed-off-by: Ismo Puustinen <ismo.puustinen@intel.com>
  • Loading branch information
ipuustin authored and htuch committed Aug 22, 2019
1 parent 57d48a3 commit 9a3a234
Show file tree
Hide file tree
Showing 37 changed files with 1,679 additions and 63 deletions.
2 changes: 2 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ extensions/filters/common/original_src @snowp @klarose
/*/extensions/filters/http/header_to_metadata @rgs1 @zuercher
# alts transport socket extension
/*/extensions/transport_sockets/alts @htuch @yangminzhu
# tls transport socket extension
/*/extensions/transport_sockets/tls @PiotrSikora @lizan
# sni_cluster extension
/*/extensions/filters/network/sni_cluster @rshriram @lizan
# tracers.datadog extension
Expand Down
27 changes: 27 additions & 0 deletions api/envoy/api/v2/auth/cert.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ option go_package = "auth";
import "envoy/api/v2/core/base.proto";
import "envoy/api/v2/core/config_source.proto";

import "google/protobuf/any.proto";
import "google/protobuf/struct.proto";
import "google/protobuf/wrappers.proto";

import "validate/validate.proto";
Expand Down Expand Up @@ -102,13 +104,38 @@ message TlsParameters {
repeated string ecdh_curves = 4;
}

// BoringSSL private key method configuration. The private key methods are used for external
// (potentially asynchronous) signing and decryption operations. Some use cases for private key
// methods would be TPM support and TLS acceleration.
message PrivateKeyProvider {
// Private key method provider name. The name must match a
// supported private key method provider type.
string provider_name = 1 [(validate.rules).string.min_bytes = 1];

// Private key method provider specific configuration.
oneof config_type {
google.protobuf.Struct config = 2;

google.protobuf.Any typed_config = 3;
}
}

message TlsCertificate {
// The TLS certificate chain.
core.DataSource certificate_chain = 1;

// The TLS private key.
core.DataSource private_key = 2;

// BoringSSL private key method provider. This is an alternative to :ref:`private_key
// <envoy_api_field_auth.TlsCertificate.private_key>` field. This can't be
// marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key
// <envoy_api_field_auth.TlsCertificate.private_key>` and
// :ref:`private_key_provider
// <envoy_api_field_auth.TlsCertificate.private_key_provider>` fields will result in an
// error.
PrivateKeyProvider private_key_provider = 6;

// The password to decrypt the TLS private key. If this field is not set, it is assumed that the
// TLS private key is not password encrypted.
core.DataSource password = 3;
Expand Down
1 change: 1 addition & 0 deletions docs/root/extending/extending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ types including:
* :ref:`Stat sinks <arch_overview_statistics>`
* :ref:`Tracers <arch_overview_tracing>`
* Transport sockets
* BoringSSL private key methods

As of this writing there is no high level extension developer documentation. The
:repo:`existing extensions <source/extensions>` are a good way to learn what is possible.
Expand Down
4 changes: 4 additions & 0 deletions docs/root/intro/arch_overview/security/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ requirements (TLS1.2, SNI, etc.). Envoy supports the following TLS features:
tickets (see `RFC 5077 <https://www.ietf.org/rfc/rfc5077.txt>`_). Resumption can be performed
across hot restarts and between parallel Envoy instances (typically useful in a front proxy
configuration).
* **BoringSSL private key methods**: TLS private key operations (signing and decrypting) can be
performed asynchronously from an extension. This allows extending Envoy to support various key
management schemes (such as TPM) and TLS acceleration. This mechanism uses
`BoringSSL private key method interface <https://github.com/google/boringssl/blob/c0b4c72b6d4c6f4828a373ec454bd646390017d4/include/openssl/ssl.h#L1169>`_.

Underlying implementation
-------------------------
Expand Down
3 changes: 3 additions & 0 deletions include/envoy/ssl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ envoy_cc_library(
envoy_cc_library(
name = "tls_certificate_config_interface",
hdrs = ["tls_certificate_config.h"],
deps = [
"//include/envoy/ssl/private_key:private_key_interface",
],
)

envoy_cc_library(
Expand Down
7 changes: 7 additions & 0 deletions include/envoy/ssl/context_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "envoy/common/time.h"
#include "envoy/ssl/context.h"
#include "envoy/ssl/context_config.h"
#include "envoy/ssl/private_key/private_key.h"
#include "envoy/stats/scope.h"

namespace Envoy {
Expand Down Expand Up @@ -39,6 +40,12 @@ class ContextManager {
* Iterate through all currently allocated contexts.
*/
virtual void iterateContexts(std::function<void(const Context&)> callback) PURE;

/**
* Access the private key operations manager, which is part of SSL
* context manager.
*/
virtual PrivateKeyMethodManager& privateKeyMethodManager() PURE;
};

using ContextManagerPtr = std::unique_ptr<ContextManager>;
Expand Down
35 changes: 35 additions & 0 deletions include/envoy/ssl/private_key/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
licenses(["notice"]) # Apache 2

load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_library",
"envoy_package",
)

envoy_package()

envoy_cc_library(
name = "private_key_interface",
hdrs = ["private_key.h"],
external_deps = ["ssl"],
deps = [
":private_key_callbacks_interface",
"//include/envoy/event:dispatcher_interface",
"@envoy_api//envoy/api/v2/auth:cert_cc",
],
)

envoy_cc_library(
name = "private_key_config_interface",
hdrs = ["private_key_config.h"],
deps = [
":private_key_interface",
"//include/envoy/registry",
],
)

envoy_cc_library(
name = "private_key_callbacks_interface",
hdrs = ["private_key_callbacks.h"],
external_deps = ["ssl"],
)
85 changes: 85 additions & 0 deletions include/envoy/ssl/private_key/private_key.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#pragma once

#include <functional>
#include <string>

#include "envoy/api/v2/auth/cert.pb.h"
#include "envoy/common/pure.h"
#include "envoy/event/dispatcher.h"
#include "envoy/ssl/private_key/private_key_callbacks.h"

#include "openssl/ssl.h"

namespace Envoy {
namespace Server {
namespace Configuration {
// Prevent a dependency loop with the forward declaration.
class TransportSocketFactoryContext;
} // namespace Configuration
} // namespace Server

namespace Ssl {

using BoringSslPrivateKeyMethodSharedPtr = std::shared_ptr<SSL_PRIVATE_KEY_METHOD>;

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

/**
* Register an SSL connection to private key operations by the provider.
* @param ssl a SSL connection object.
* @param cb a callbacks object, whose "complete" method will be invoked
* when the asynchronous processing is complete.
* @param dispatcher supplies the owning thread's dispatcher.
*/
virtual void registerPrivateKeyMethod(SSL* ssl, PrivateKeyConnectionCallbacks& cb,
Event::Dispatcher& dispatcher) PURE;

/**
* Unregister an SSL connection from private key operations by the provider.
* @param ssl a SSL connection object.
* @throw EnvoyException if registration fails.
*/
virtual void unregisterPrivateKeyMethod(SSL* ssl) PURE;

/**
* Check whether the private key method satisfies FIPS requirements.
* @return true if FIPS key requirements are satisfied, false if not.
*/
virtual bool checkFips() PURE;

/**
* Get the private key methods from the provider.
* @return the private key methods associated with this provider and
* configuration.
*/
virtual BoringSslPrivateKeyMethodSharedPtr getBoringSslPrivateKeyMethod() PURE;
};

using PrivateKeyMethodProviderSharedPtr = std::shared_ptr<PrivateKeyMethodProvider>;

/**
* A manager for finding correct user-provided functions for handling BoringSSL private key
* operations.
*/
class PrivateKeyMethodManager {
public:
virtual ~PrivateKeyMethodManager() = default;

/**
* Finds and returns a private key operations provider for BoringSSL.
*
* @param config a protobuf message object containing a PrivateKeyProvider message.
* @param factory_context context that provides components for creating and
* initializing connections using asynchronous private key operations.
* @return PrivateKeyMethodProvider the private key operations provider, or nullptr if
* no provider can be used with the context configuration.
*/
virtual PrivateKeyMethodProviderSharedPtr createPrivateKeyMethodProvider(
const envoy::api::v2::auth::PrivateKeyProvider& config,
Envoy::Server::Configuration::TransportSocketFactoryContext& factory_context) PURE;
};

} // namespace Ssl
} // namespace Envoy
25 changes: 25 additions & 0 deletions include/envoy/ssl/private_key/private_key_callbacks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <functional>
#include <string>

#include "envoy/common/pure.h"

namespace Envoy {
namespace Ssl {

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

/**
* Callback function which is called when the asynchronous private key
* operation has been completed (with either success or failure). The
* provider will communicate the success status when SSL_do_handshake()
* is called the next time.
*/
virtual void onPrivateKeyMethodComplete() PURE;
};

} // namespace Ssl
} // namespace Envoy
22 changes: 22 additions & 0 deletions include/envoy/ssl/private_key/private_key_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include "envoy/api/v2/auth/cert.pb.h"
#include "envoy/registry/registry.h"
#include "envoy/ssl/private_key/private_key.h"

namespace Envoy {
namespace Ssl {

// Base class which the private key operation provider implementations can register.

class PrivateKeyMethodProviderInstanceFactory {
public:
virtual ~PrivateKeyMethodProviderInstanceFactory() = default;
virtual PrivateKeyMethodProviderSharedPtr createPrivateKeyMethodProviderInstance(
const envoy::api::v2::auth::PrivateKeyProvider& config,
Server::Configuration::TransportSocketFactoryContext& factory_context) PURE;
virtual std::string name() const PURE;
};

} // namespace Ssl
} // namespace Envoy
6 changes: 6 additions & 0 deletions include/envoy/ssl/tls_certificate_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <string>

#include "envoy/common/pure.h"
#include "envoy/ssl/private_key/private_key.h"

namespace Envoy {
namespace Ssl {
Expand Down Expand Up @@ -34,6 +35,11 @@ class TlsCertificateConfig {
*/
virtual const std::string& privateKeyPath() const PURE;

/**
* @return private key method provider.
*/
virtual Envoy::Ssl::PrivateKeyMethodProviderSharedPtr privateKeyMethod() const PURE;

/**
* @return a string of password.
*/
Expand Down
2 changes: 2 additions & 0 deletions source/common/ssl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ envoy_cc_library(
srcs = ["tls_certificate_config_impl.cc"],
hdrs = ["tls_certificate_config_impl.h"],
deps = [
"//include/envoy/server:transport_socket_config_interface",
"//include/envoy/ssl:tls_certificate_config_interface",
"//include/envoy/ssl/private_key:private_key_interface",
"//source/common/common:empty_string",
"//source/common/config:datasource_lib",
"@envoy_api//envoy/api/v2/auth:cert_cc",
Expand Down
19 changes: 15 additions & 4 deletions source/common/ssl/tls_certificate_config_impl.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "common/ssl/tls_certificate_config_impl.h"

#include "envoy/common/exception.h"
#include "envoy/server/transport_socket_config.h"

#include "common/common/empty_string.h"
#include "common/common/fmt.h"
Expand All @@ -12,7 +13,8 @@ namespace Ssl {
static const std::string INLINE_STRING = "<inline>";

TlsCertificateConfigImpl::TlsCertificateConfigImpl(
const envoy::api::v2::auth::TlsCertificate& config, Api::Api& api)
const envoy::api::v2::auth::TlsCertificate& config,
Server::Configuration::TransportSocketFactoryContext* factory_context, Api::Api& api)
: certificate_chain_(Config::DataSource::read(config.certificate_chain(), true, api)),
certificate_chain_path_(
Config::DataSource::getPath(config.certificate_chain())
Expand All @@ -22,9 +24,18 @@ TlsCertificateConfigImpl::TlsCertificateConfigImpl(
.value_or(private_key_.empty() ? EMPTY_STRING : INLINE_STRING)),
password_(Config::DataSource::read(config.password(), true, api)),
password_path_(Config::DataSource::getPath(config.password())
.value_or(password_.empty() ? EMPTY_STRING : INLINE_STRING)) {

if (certificate_chain_.empty() || private_key_.empty()) {
.value_or(password_.empty() ? EMPTY_STRING : INLINE_STRING)),
private_key_method_(
factory_context != nullptr && config.has_private_key_provider()
? factory_context->sslContextManager()
.privateKeyMethodManager()
.createPrivateKeyMethodProvider(config.private_key_provider(), *factory_context)
: nullptr) {
if (config.has_private_key_provider() && config.has_private_key()) {
throw EnvoyException(fmt::format(
"Certificate configuration can't have both private_key and private_key_provider"));
}
if (certificate_chain_.empty() || (private_key_.empty() && private_key_method_ == nullptr)) {
throw EnvoyException(fmt::format("Failed to load incomplete certificate from {}, {}",
certificate_chain_path_, private_key_path_));
}
Expand Down
9 changes: 8 additions & 1 deletion source/common/ssl/tls_certificate_config_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@

#include "envoy/api/api.h"
#include "envoy/api/v2/auth/cert.pb.h"
#include "envoy/ssl/private_key/private_key.h"
#include "envoy/ssl/tls_certificate_config.h"

namespace Envoy {
namespace Ssl {

class TlsCertificateConfigImpl : public TlsCertificateConfig {
public:
TlsCertificateConfigImpl(const envoy::api::v2::auth::TlsCertificate& config, Api::Api& api);
TlsCertificateConfigImpl(const envoy::api::v2::auth::TlsCertificate& config,
Server::Configuration::TransportSocketFactoryContext* factory_context,
Api::Api& api);

const std::string& certificateChain() const override { return certificate_chain_; }
const std::string& certificateChainPath() const override { return certificate_chain_path_; }
const std::string& privateKey() const override { return private_key_; }
const std::string& privateKeyPath() const override { return private_key_path_; }
const std::string& password() const override { return password_; }
const std::string& passwordPath() const override { return password_path_; }
Envoy::Ssl::PrivateKeyMethodProviderSharedPtr privateKeyMethod() const override {
return private_key_method_;
}

private:
const std::string certificate_chain_;
Expand All @@ -27,6 +33,7 @@ class TlsCertificateConfigImpl : public TlsCertificateConfig {
const std::string private_key_path_;
const std::string password_;
const std::string password_path_;
Envoy::Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_{};
};

} // namespace Ssl
Expand Down
Loading

0 comments on commit 9a3a234

Please sign in to comment.