Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tls: add support for private keys with passphrase #5175

Merged
merged 4 commits into from
Dec 9, 2018
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
3 changes: 2 additions & 1 deletion api/envoy/api/v2/auth/cert.proto
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ message TlsCertificate {
// The TLS private key.
core.DataSource private_key = 2;

// [#not-implemented-hide:]
// 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;

// [#not-implemented-hide:]
Expand Down
5 changes: 3 additions & 2 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ Version history
its behaviour within TCP and HTTP implementations.
* stream: renamed `perRequestState` to `filterState` in `StreamInfo`.
* thrift_proxy: introduced thrift rate limiter filter
* tls: add support for :ref:`client-side session resumption <envoy_api_field_auth.UpstreamTlsContext.max_session_keys>`.
* tls: add support for CRLs in :ref:`trusted_ca <envoy_api_field_auth.CertificateValidationContext.trusted_ca>`.
* tls: added support for :ref:`client-side session resumption <envoy_api_field_auth.UpstreamTlsContext.max_session_keys>`.
* tls: added support for CRLs in :ref:`trusted_ca <envoy_api_field_auth.CertificateValidationContext.trusted_ca>`.
* tls: added support for :ref:`password encrypted private keys <envoy_api_field_auth.TlsCertificate.password>`.
* tracing: added support to the Zipkin tracer for the :ref:`b3 <config_http_conn_man_headers_b3>` single header format.
* tracing: added support for :ref:`Datadog <arch_overview_tracing>` tracer.
* upstream: added :ref:`scale_locality_weight<envoy_api_field_Cluster.LbSubsetConfig.scale_locality_weight>` to enable
Expand Down
23 changes: 17 additions & 6 deletions include/envoy/ssl/tls_certificate_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,37 @@ class TlsCertificateConfig {
virtual ~TlsCertificateConfig() {}

/**
* @return a string of certificate chain
* @return a string of certificate chain.
*/
virtual const std::string& certificateChain() const PURE;

/**
* @return Path of the certificate chain used to identify the local side or "<inline>"
* if the certificate chain was inlined.
* @return path of the certificate chain used to identify the local side or "<inline>" if the
* certificate chain was inlined.
*/
virtual const std::string& certificateChainPath() const PURE;

/**
* @return a string of private key
* @return a string of private key.
*/
virtual const std::string& privateKey() const PURE;

/**
* @return Path of the private key used to identify the local side or "<inline>"
* if the private key was inlined.
* @return path of the private key used to identify the local side or "<inline>" if the private
* key was inlined.
*/
virtual const std::string& privateKeyPath() const PURE;

/**
* @return a string of password.
*/
virtual const std::string& password() const PURE;

/**
* @return path of the password file to be used to decrypt the private key or "<inline>" if the
* password was inlined.
*/
virtual const std::string& passwordPath() const PURE;
};

typedef std::unique_ptr<TlsCertificateConfig> TlsCertificateConfigPtr;
Expand Down
5 changes: 4 additions & 1 deletion source/common/ssl/context_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,10 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const ContextConfig& config, TimeS
bio.reset(BIO_new_mem_buf(const_cast<char*>(tls_certificate.privateKey().data()),
tls_certificate.privateKey().size()));
RELEASE_ASSERT(bio != nullptr, "");
bssl::UniquePtr<EVP_PKEY> pkey(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr));
bssl::UniquePtr<EVP_PKEY> pkey(PEM_read_bio_PrivateKey(
bio.get(), nullptr, nullptr,
!tls_certificate.password().empty() ? const_cast<char*>(tls_certificate.password().c_str())
Copy link
Member

Choose a reason for hiding this comment

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

This const_cast makes me sad. No way around this I guess @PiotrSikora?

: nullptr));
if (pkey == nullptr || !SSL_CTX_use_PrivateKey(ctx.ssl_ctx_.get(), pkey.get())) {
throw EnvoyException(
fmt::format("Failed to load private key from {}", tls_certificate.privateKeyPath()));
Expand Down
5 changes: 4 additions & 1 deletion source/common/ssl/tls_certificate_config_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ TlsCertificateConfigImpl::TlsCertificateConfigImpl(
.value_or(certificate_chain_.empty() ? EMPTY_STRING : INLINE_STRING)),
private_key_(Config::DataSource::read(config.private_key(), true)),
private_key_path_(Config::DataSource::getPath(config.private_key())
.value_or(private_key_.empty() ? EMPTY_STRING : INLINE_STRING)) {
.value_or(private_key_.empty() ? EMPTY_STRING : INLINE_STRING)),
password_(Config::DataSource::read(config.password(), true)),
password_path_(Config::DataSource::getPath(config.password())
.value_or(password_.empty() ? EMPTY_STRING : INLINE_STRING)) {

if (certificate_chain_.empty() || private_key_.empty()) {
throw EnvoyException(fmt::format("Failed to load incomplete certificate from {}, {}",
Expand Down
4 changes: 4 additions & 0 deletions source/common/ssl/tls_certificate_config_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ class TlsCertificateConfigImpl : public TlsCertificateConfig {
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_; }

private:
const std::string certificate_chain_;
const std::string certificate_chain_path_;
const std::string private_key_;
const std::string private_key_path_;
const std::string password_;
const std::string password_path_;
};

} // namespace Ssl
Expand Down
69 changes: 69 additions & 0 deletions test/common/ssl/context_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,75 @@ name: "abc.com"
client_context_config.tlsCertificates()[0].get().privateKey());
}

// Validate that client context config with password-protected TLS certificates is created
// successfully.
TEST(ClientContextConfigImplTest, PasswordProtectedTlsCertificates) {
envoy::api::v2::auth::Secret secret_config;
secret_config.set_name("abc.com");

auto* tls_certificate = secret_config.mutable_tls_certificate();
tls_certificate->mutable_certificate_chain()->set_filename(TestEnvironment::substitute(
"{{ test_rundir }}/test/common/ssl/test_data/password_protected_cert.pem"));
tls_certificate->mutable_private_key()->set_filename(TestEnvironment::substitute(
"{{ test_rundir }}/test/common/ssl/test_data/password_protected_key.pem"));
tls_certificate->mutable_password()->set_filename(
TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/password.txt"));

envoy::api::v2::auth::UpstreamTlsContext tls_context;
tls_context.mutable_common_tls_context()
->mutable_tls_certificate_sds_secret_configs()
->Add()
->set_name("abc.com");

NiceMock<Server::Configuration::MockTransportSocketFactoryContext> factory_context;
factory_context.secretManager().addStaticSecret(secret_config);
ClientContextConfigImpl client_context_config(tls_context, factory_context);

const std::string cert_pem =
"{{ test_rundir }}/test/common/ssl/test_data/password_protected_cert.pem";
EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)),
client_context_config.tlsCertificates()[0].get().certificateChain());
const std::string key_pem =
"{{ test_rundir }}/test/common/ssl/test_data/password_protected_key.pem";
EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(key_pem)),
client_context_config.tlsCertificates()[0].get().privateKey());
const std::string password_file = "{{ test_rundir }}/test/common/ssl/test_data/password.txt";
EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(password_file)),
client_context_config.tlsCertificates()[0].get().password());
}

// Validate that not supplying a passphrase for password-protected TLS certificates
// triggers a failure.
TEST(ClientContextConfigImplTest, PasswordNotSuppliedTlsCertificates) {
envoy::api::v2::auth::Secret secret_config;
secret_config.set_name("abc.com");

auto* tls_certificate = secret_config.mutable_tls_certificate();
tls_certificate->mutable_certificate_chain()->set_filename(TestEnvironment::substitute(
"{{ test_rundir }}/test/common/ssl/test_data/password_protected_cert.pem"));
const std::string private_key_path = TestEnvironment::substitute(
"{{ test_rundir }}/test/common/ssl/test_data/password_protected_key.pem");
tls_certificate->mutable_private_key()->set_filename(private_key_path);
// Don't supply the password.

envoy::api::v2::auth::UpstreamTlsContext tls_context;
tls_context.mutable_common_tls_context()
->mutable_tls_certificate_sds_secret_configs()
->Add()
->set_name("abc.com");

NiceMock<Server::Configuration::MockTransportSocketFactoryContext> factory_context;
factory_context.secretManager().addStaticSecret(secret_config);
ClientContextConfigImpl client_context_config(tls_context, factory_context);

Event::SimulatedTimeSystem time_system;
ContextManagerImpl manager(time_system);
Stats::IsolatedStoreImpl store;
EXPECT_THROW_WITH_REGEX(manager.createSslClientContext(store, client_context_config),
EnvoyException,
fmt::format("Failed to load private key from {}", private_key_path));
}

// Validate that client context config with static certificate validation context is created
// successfully.
TEST(ClientContextConfigImplTest, StaticCertificateValidationContext) {
Expand Down
45 changes: 45 additions & 0 deletions test/common/ssl/ssl_socket_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,51 @@ TEST_P(SslSocketTest, FailedClientCertificateHashVerificationWrongCA) {
false, GetParam());
}

TEST_P(SslSocketTest, CertificatesWithPassword) {
envoy::api::v2::Listener listener;
envoy::api::v2::listener::FilterChain* filter_chain = listener.add_filter_chains();
envoy::api::v2::auth::TlsCertificate* server_cert =
filter_chain->mutable_tls_context()->mutable_common_tls_context()->add_tls_certificates();
server_cert->mutable_certificate_chain()->set_filename(TestEnvironment::substitute(
"{{ test_rundir }}/test/common/ssl/test_data/password_protected_cert.pem"));
server_cert->mutable_private_key()->set_filename(TestEnvironment::substitute(
"{{ test_rundir }}/test/common/ssl/test_data/password_protected_key.pem"));
server_cert->mutable_password()->set_filename(
TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/password.txt"));
envoy::api::v2::auth::CertificateValidationContext* server_validation_ctx =
filter_chain->mutable_tls_context()
->mutable_common_tls_context()
->mutable_validation_context();
server_validation_ctx->mutable_trusted_ca()->set_filename(
TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/ca_cert.pem"));
server_validation_ctx->add_verify_certificate_hash(
"0000000000000000000000000000000000000000000000000000000000000000");
server_validation_ctx->add_verify_certificate_hash(
"ceefb953bb940c94e2e88f82e2af3ee43611bdad522bf4595e8d1acff3b500fc");

envoy::api::v2::auth::UpstreamTlsContext client;
envoy::api::v2::auth::TlsCertificate* client_cert =
client.mutable_common_tls_context()->add_tls_certificates();
client_cert->mutable_certificate_chain()->set_filename(TestEnvironment::substitute(
"{{ test_rundir }}/test/common/ssl/test_data/password_protected_cert.pem"));
client_cert->mutable_private_key()->set_filename(TestEnvironment::substitute(
"{{ test_rundir }}/test/common/ssl/test_data/password_protected_key.pem"));
client_cert->mutable_password()->set_inline_string(TestEnvironment::readFileToStringForTest(
TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/password.txt")));

testUtilV2(listener, client, "", true, "",
"ceefb953bb940c94e2e88f82e2af3ee43611bdad522bf4595e8d1acff3b500fc",
"spiffe://lyft.com/test-team", "", "", "ssl.handshake", "ssl.handshake", GetParam(),
nullptr);

// Works even with client renegotiation.
client.set_allow_renegotiation(true);
testUtilV2(listener, client, "", true, "",
"ceefb953bb940c94e2e88f82e2af3ee43611bdad522bf4595e8d1acff3b500fc",
"spiffe://lyft.com/test-team", "", "", "ssl.handshake", "ssl.handshake", GetParam(),
nullptr);
}

venilnoronha marked this conversation as resolved.
Show resolved Hide resolved
TEST_P(SslSocketTest, ClientCertificateSpkiVerification) {
envoy::api::v2::Listener listener;
envoy::api::v2::listener::FilterChain* filter_chain = listener.add_filter_chains();
Expand Down
5 changes: 4 additions & 1 deletion test/common/ssl/test_data/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# What are the identities, certificates and keys
There are 10 identities:
There are 11 identities:
- **CA**: Certificate Authority for **No SAN**, **SAN With URI** and **SAN With
DNS**. It has the self-signed certificate *ca_cert.pem*. *ca_key.pem* is its
private key. Additionally, we create a CRL for this CA (*ca_cert.crl*) that
Expand All @@ -12,6 +12,9 @@ There are 10 identities:
its private key.
- **No SAN**: It has the certificate *no_san_cert.pem*, signed by the **CA**.
The certificate does not have SAN field. *no_san_key.pem* is its private key.
- **Password-protected**: The password-protected certificate *password_protected_cert.pem*,
using the config *san_uri_cert.cfg*. *password_protected_key.pem* is
its private key encrypted using the password supplied in *password.txt*.
- **SAN With URI**: It has the certificate *san_uri_cert.pem*, which is signed
by the **CA** using the config *san_uri_cert.cfg*. The certificate has SAN
field of URI type. *san_uri_key.pem* is its private key.
Expand Down
5 changes: 5 additions & 0 deletions test/common/ssl/test_data/certs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ set -e
# openssl genrsa -out intermediate_ca_key.pem 1024
# openssl genrsa -out fake_ca_key.pem 1024
# openssl genrsa -out no_san_key.pem 1024
# openssl genrsa -aes128 -passout file:password.txt -out password_protected_key.pem 1024
# openssl genrsa -out san_dns_key.pem 1024
# openssl genrsa -out san_dns_key2.pem 1024
# openssl genrsa -out san_dns_key3.pem 1024
Expand Down Expand Up @@ -35,6 +36,10 @@ cat fake_ca_cert.pem ca_cert.pem > ca_certificates.pem
openssl req -new -key no_san_key.pem -out no_san_cert.csr -config no_san_cert.cfg -batch -sha256
openssl x509 -req -days 730 -in no_san_cert.csr -sha256 -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out no_san_cert.pem -extensions v3_ca -extfile no_san_cert.cfg

# Generate password_protected_cert.pem.
openssl req -new -key password_protected_key.pem -out password_protected_cert.csr -config san_uri_cert.cfg -batch -sha256 -passin file:password.txt
openssl x509 -req -days 730 -in password_protected_cert.csr -sha256 -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out password_protected_cert.pem -extensions v3_ca -extfile san_uri_cert.cfg -passin file:password.txt

# Generate san_dns_cert.pem.
openssl req -new -key san_dns_key.pem -out san_dns_cert.csr -config san_dns_cert.cfg -batch -sha256
openssl x509 -req -days 730 -in san_dns_cert.csr -sha256 -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out san_dns_cert.pem -extensions v3_ca -extfile san_dns_cert.cfg
Expand Down
1 change: 1 addition & 0 deletions test/common/ssl/test_data/password.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
p4$$w0rd
19 changes: 19 additions & 0 deletions test/common/ssl/test_data/password_protected_cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDFDCCAn2gAwIBAgIJAMmBbUr4Ee+qMA0GCSqGSIb3DQEBCwUAMHYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNp
c2NvMQ0wCwYDVQQKEwRMeWZ0MRkwFwYDVQQLExBMeWZ0IEVuZ2luZWVyaW5nMRAw
DgYDVQQDEwdUZXN0IENBMB4XDTE4MTIwMzE2NDI0M1oXDTIwMTIwMjE2NDI0M1ow
ejELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNh
biBGcmFuY2lzY28xDTALBgNVBAoMBEx5ZnQxGTAXBgNVBAsMEEx5ZnQgRW5naW5l
ZXJpbmcxFDASBgNVBAMMC1Rlc3QgU2VydmVyMIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQCxIvsnye8NedCOwPBdUoUjZllyiwrUrgs320kwb+ltRbbl5bIEFeUv
k9yA9IvIh/M/bEqqIqhIbytzMZx20Td/HpN8SBILg+d+dO1bxoAu036gKKXEwvA8
SjcOwHm1y9DMGEPoxQvtQagSd6HrsylWnxpDB25i5ElREFBEU0b4EwIDAQABo4Gl
MIGiMAwGA1UdEwEB/wQCMAAwCwYDVR0PBAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUF
BwMCBggrBgEFBQcDATAmBgNVHREEHzAdhhtzcGlmZmU6Ly9seWZ0LmNvbS90ZXN0
LXRlYW0wHQYDVR0OBBYEFOZp246/2f+xw0WeWDuEvcOAF6gVMB8GA1UdIwQYMBaA
FDt4pFFPFoSTHEgoegytK5ZByn15MA0GCSqGSIb3DQEBCwUAA4GBACLOPZty0e+j
X6yBVv2/97HopvvhoNaTmOrld1Z4jgNGZDBhHFMGearMJyXAq7gDLZOvQAiv8XmZ
p5O2BYcKGhXuu0mHe4YBPO14W2VQ7/IYMFknUY3VtThL+HTmhwhOa3HS9xEVbTN6
y571yYUPAJ/0k6I7NBqG/Pq6qznc7JoF
-----END CERTIFICATE-----
18 changes: 18 additions & 0 deletions test/common/ssl/test_data/password_protected_key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,72BDD5B319B0D857C23406D3152BE4F5

T3TNMWxx6C+tEroxtEh/0hVD7fOgv+J6sJZ1n7HHby/JraZLv5seY2VyOPFQCGNt
t1f2TifLHUrPXR5d2G2Mup9WR052erpaSk5RBAJRJtAHvEi+zSnLDrL5p8JuskKh
NZYu0sY6dLvBgKiVyRU/o04PlFj6I6tm1o9OXUkC+4GR/JG/8GOfKwHS9j6ZqRi0
OOJ+NHZRDxVIFky5YRihsaUaw844xcVC9RNjFrlXMLzXfKO1C4n2n3jh2xkRB1za
PBeY909JET0BwEtm9IA6x39xsb0JP+N8eDpoAlN8M+YfYXhp1p57opUMQDqhw70e
cma2kaCNoQOwlKPgTReOPmzy6l8uRvA9JiNxq4aMCAxnsHsNZVmlP1qzKz4wxGmI
ayoDs8Y1C5j9+EWOHdjxHmngMT+8jsIVHgVsfHKlUDXD8qLLFzTFh6QAmxY374Tl
gdNKW0xTi980snJCOojZQzpyTM8CoHjj0kRrgs8vjB/bEwhotH5wc6aIobbDtzyC
IfMC2vZ5gQiveXcx2TYyPA00V9qPMtjKVZ+wDpkRXHcJlst+9xYLVi9CqEPP6F9V
+w/TEk+ks9c8aHnIXDZhcdEmqISR9L2h32dB9iEAe8KDq/BKmr3sXkXE2bDQxPlG
tv3kbaSgXGlRhq9WckN4U7fudrOdFxDVzqypoBhwk9LI396OYrxE8psnle1387M3
Hs85ord+s83asne3964Mn/IhWPk4fdKeQ8A79wKrlG1Zx/aKgH/wvIeMxF0GDswJ
TAVls5eZN2Z2+49aadPX+LQhBmK7rveSUCcktxY82PEMUuW17JMyjVOetb2M1QX1
-----END RSA PRIVATE KEY-----