summaryrefslogtreecommitdiffstats
path: root/vespalib
diff options
context:
space:
mode:
authorTor Brede Vekterli <vekterli@oath.com>2018-11-13 12:48:56 +0000
committerTor Brede Vekterli <vekterli@oath.com>2019-01-24 12:29:20 +0000
commit04f493deab394c70d57472f7971a10e4a6a4e85b (patch)
treec9a1d6f37c15c865f45438c8e10834e20466c6bb /vespalib
parent2a9c88419abf98fb0cb67f13aa5da1c44cf5d99a (diff)
Add support for default cipher suite and `accepted-ciphers` config in C++
Since the TLS config file uses IANA cipher names but OpenSSL uses its own cipher spec format internally, we explicitly remap the provided names. We only support a modern subset of ciphers. The default cipher suite contains ciphers that work across both TLSv1.2 and TLSv1.3.
Diffstat (limited to 'vespalib')
-rw-r--r--vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp18
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/impl/CMakeLists.txt1
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/impl/iana_cipher_map.cpp53
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/impl/iana_cipher_map.h25
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp37
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h1
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/transport_security_options.cpp6
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/transport_security_options.h10
-rw-r--r--vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp23
9 files changed, 168 insertions, 6 deletions
diff --git a/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp b/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp
index c1e2c0b5f49..a54e2f29aa1 100644
--- a/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp
+++ b/vespalib/src/tests/net/tls/transport_options/transport_options_reading_test.cpp
@@ -137,6 +137,24 @@ TEST("empty required-credentials array throws exception") {
"\"required-credentials\" array can't be empty (would allow all peers)");
}
+TEST("accepted cipher list is empty if not specified") {
+ const char* json = R"({"files":{"private-key":"dummy_privkey.txt",
+ "certificates":"dummy_certs.txt",
+ "ca-certificates":"dummy_ca_certs.txt"}})";
+ EXPECT_TRUE(read_options_from_json_string(json)->accepted_ciphers().empty());
+}
+
+TEST("accepted cipher list is populated if specified") {
+ const char* json = R"({"files":{"private-key":"dummy_privkey.txt",
+ "certificates":"dummy_certs.txt",
+ "ca-certificates":"dummy_ca_certs.txt"},
+ "accepted-ciphers":["foo", "bar"]})";
+ auto ciphers = read_options_from_json_string(json)->accepted_ciphers();
+ ASSERT_EQUAL(2u, ciphers.size());
+ EXPECT_EQUAL("foo", ciphers[0]);
+ EXPECT_EQUAL("bar", ciphers[1]);
+}
+
// TODO test parsing of multiple policies
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/CMakeLists.txt b/vespalib/src/vespa/vespalib/net/tls/impl/CMakeLists.txt
index 6129fde3c6c..d69da590311 100644
--- a/vespalib/src/vespa/vespalib/net/tls/impl/CMakeLists.txt
+++ b/vespalib/src/vespa/vespalib/net/tls/impl/CMakeLists.txt
@@ -2,6 +2,7 @@
vespa_add_library(vespalib_vespalib_net_tls_impl OBJECT
SOURCES
direct_buffer_bio.cpp
+ iana_cipher_map.cpp
openssl_tls_context_impl.cpp
openssl_crypto_codec_impl.cpp
DEPENDS
diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/iana_cipher_map.cpp b/vespalib/src/vespa/vespalib/net/tls/impl/iana_cipher_map.cpp
new file mode 100644
index 00000000000..5e60714efea
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/net/tls/impl/iana_cipher_map.cpp
@@ -0,0 +1,53 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "iana_cipher_map.h"
+#include <vespa/vespalib/stllike/hash_fun.h>
+#include <utility>
+#include <unordered_map>
+
+namespace vespalib::net::tls {
+
+using vespalib::stringref;
+using CipherMapType = std::unordered_map<stringref, stringref, vespalib::hash<stringref>>;
+
+namespace {
+
+const CipherMapType& modern_cipher_suites_iana_to_openssl() {
+ // Handpicked subset of supported ciphers from https://www.openssl.org/docs/manmaster/man1/ciphers.html
+ // based on Modern spec from https://wiki.mozilla.org/Security/Server_Side_TLS
+ // For TLSv1.2 we only allow RSA and ECDSA with ephemeral key exchange and GCM.
+ // For TLSv1.3 we allow the DEFAULT group ciphers.
+ // Note that we _only_ allow AEAD ciphers for either TLS version.
+ static CipherMapType ciphers({
+ {"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "ECDHE-RSA-AES128-GCM-SHA256"},
+ {"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "ECDHE-RSA-AES256-GCM-SHA384"},
+ {"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "ECDHE-ECDSA-AES128-GCM-SHA256"},
+ {"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "ECDHE-ECDSA-AES256-GCM-SHA384"},
+ {"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "ECDHE-RSA-CHACHA20-POLY1305"},
+ {"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "ECDHE-ECDSA-CHACHA20-POLY1305"},
+ {"TLS_AES_128_GCM_SHA256", "TLS13-AES-128-GCM-SHA256"},
+ {"TLS_AES_256_GCM_SHA384", "TLS13-AES-256-GCM-SHA384"},
+ {"TLS_CHACHA20_POLY1305_SHA256", "TLS13-CHACHA20-POLY1305-SHA256"}
+ });
+ return ciphers;
+}
+
+} // anon ns
+
+const char* iana_cipher_to_openssl_cipher(vespalib::stringref iana_name) {
+ const auto& ciphers = modern_cipher_suites_iana_to_openssl();
+ auto iter = ciphers.find(iana_name);
+ return ((iter != ciphers.end()) ? iter->second.data() : nullptr);
+}
+
+std::vector<vespalib::string> modern_iana_cipher_suite() {
+ const auto& ciphers = modern_cipher_suites_iana_to_openssl();
+ std::vector<vespalib::string> iana_cipher_names;
+ iana_cipher_names.reserve(ciphers.size());
+ for (auto& cipher : ciphers) {
+ iana_cipher_names.emplace_back(cipher.first);
+ }
+ return iana_cipher_names;
+}
+
+}
diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/iana_cipher_map.h b/vespalib/src/vespa/vespalib/net/tls/impl/iana_cipher_map.h
new file mode 100644
index 00000000000..b41977f4e29
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/net/tls/impl/iana_cipher_map.h
@@ -0,0 +1,25 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/vespalib/stllike/string.h>
+
+#include <vector>
+
+namespace vespalib::net::tls {
+
+/**
+ * Returns the OpenSSL cipher name for a given IANA cipher name, or nullptr if
+ * there is no known mapping.
+ *
+ * Note that this only covers a very restricted subset of the existing IANA ciphers.
+ */
+const char* iana_cipher_to_openssl_cipher(vespalib::stringref iana_name);
+
+/**
+ * Returns a vector of all IANA cipher names that we support internally.
+ * It is guaranteed that any cipher name returned from this function will
+ * have a non-nullptr return value from iana_cipher_to_openssl_cipher(name).
+ */
+std::vector<vespalib::string> modern_iana_cipher_suite();
+
+}
diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp
index 130847b49a7..b835584a2b3 100644
--- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp
@@ -1,4 +1,5 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "iana_cipher_map.h"
#include "openssl_typedefs.h"
#include "openssl_tls_context_impl.h"
#include <vespa/vespalib/net/tls/crypto_exception.h>
@@ -210,8 +211,13 @@ OpenSslTlsContextImpl::OpenSslTlsContextImpl(
disable_renegotiation();
enforce_peer_certificate_verification();
set_ssl_ctx_self_reference();
- // TODO set accepted cipher suites!
- // TODO `--> If not set in options, use Modern spec from https://wiki.mozilla.org/Security/Server_Side_TLS
+ if (!ts_opts.accepted_ciphers().empty()) {
+ // Due to how we resolve provided ciphers, this implicitly provides an
+ // _intersection_ between our default cipher suite and the configured one.
+ set_accepted_cipher_suite(ts_opts.accepted_ciphers());
+ } else {
+ set_accepted_cipher_suite(modern_iana_cipher_suite());
+ }
}
OpenSslTlsContextImpl::~OpenSslTlsContextImpl() {
@@ -469,4 +475,31 @@ void OpenSslTlsContextImpl::set_ssl_ctx_self_reference() {
SSL_CTX_set_app_data(_ctx.get(), this);
}
+void OpenSslTlsContextImpl::set_accepted_cipher_suite(const std::vector<vespalib::string>& ciphers) {
+ vespalib::string openssl_ciphers;
+ size_t bad_ciphers = 0;
+ for (auto& iana_cipher : ciphers) {
+ auto our_cipher = iana_cipher_to_openssl_cipher(iana_cipher);
+ if (our_cipher) {
+ if (!openssl_ciphers.empty()) {
+ openssl_ciphers += ':';
+ }
+ openssl_ciphers += our_cipher;
+ } else {
+ LOG(warning, "Unsupported cipher: '%s' (bad name or unknown IANA -> OpenSSL mapping)", iana_cipher.c_str());
+ ++bad_ciphers;
+ }
+ }
+ if (bad_ciphers > 0) {
+ LOG(warning, "A total of %zu configured cipher names were not added to the set of allowed TLS ciphers. "
+ "Vespa only supports TLS ciphers with forward secrecy and AEAD properties", bad_ciphers);
+ }
+ if (openssl_ciphers.empty()) {
+ throw CryptoException("Configured cipher suite does not contain any supported ciphers");
+ }
+ if (::SSL_CTX_set_cipher_list(_ctx.get(), openssl_ciphers.c_str()) != 1) {
+ throw CryptoException("SSL_CTX_set_cipher_list failed; no provided ciphers could be used");
+ }
+}
+
}
diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h
index 6b72dfe84ee..93976f37523 100644
--- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h
+++ b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h
@@ -43,6 +43,7 @@ private:
void disable_renegotiation();
void enforce_peer_certificate_verification();
void set_ssl_ctx_self_reference();
+ void set_accepted_cipher_suite(const std::vector<vespalib::string>& ciphers);
bool verify_trusted_certificate(::X509_STORE_CTX* store_ctx);
diff --git a/vespalib/src/vespa/vespalib/net/tls/transport_security_options.cpp b/vespalib/src/vespa/vespalib/net/tls/transport_security_options.cpp
index e79fee6cabf..d4fa2ede559 100644
--- a/vespalib/src/vespa/vespalib/net/tls/transport_security_options.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/transport_security_options.cpp
@@ -10,7 +10,8 @@ TransportSecurityOptions::TransportSecurityOptions(Builder builder)
: _ca_certs_pem(std::move(builder._ca_certs_pem)),
_cert_chain_pem(std::move(builder._cert_chain_pem)),
_private_key_pem(std::move(builder._private_key_pem)),
- _authorized_peers(std::move(builder._authorized_peers))
+ _authorized_peers(std::move(builder._authorized_peers)),
+ _accepted_ciphers(std::move(builder._accepted_ciphers))
{
}
@@ -35,6 +36,9 @@ TransportSecurityOptions::TransportSecurityOptions(vespalib::string ca_certs_pem
{
}
+TransportSecurityOptions::Builder::Builder() = default;
+TransportSecurityOptions::Builder::~Builder() = default;
+
TransportSecurityOptions::~TransportSecurityOptions() {
OPENSSL_cleanse(&_private_key_pem[0], _private_key_pem.size());
}
diff --git a/vespalib/src/vespa/vespalib/net/tls/transport_security_options.h b/vespalib/src/vespa/vespalib/net/tls/transport_security_options.h
index 0502276f7a6..647e290d985 100644
--- a/vespalib/src/vespa/vespalib/net/tls/transport_security_options.h
+++ b/vespalib/src/vespa/vespalib/net/tls/transport_security_options.h
@@ -13,6 +13,7 @@ class TransportSecurityOptions {
vespalib::string _cert_chain_pem;
vespalib::string _private_key_pem;
AuthorizedPeers _authorized_peers;
+ std::vector<vespalib::string> _accepted_ciphers;
public:
TransportSecurityOptions() = default;
@@ -21,11 +22,19 @@ public:
vespalib::string _cert_chain_pem;
vespalib::string _private_key_pem;
AuthorizedPeers _authorized_peers;
+ std::vector<vespalib::string> _accepted_ciphers;
+
+ Builder();
+ ~Builder();
Builder& ca_certs_pem(vespalib::stringref pem) { _ca_certs_pem = pem; return *this; }
Builder& cert_chain_pem(vespalib::stringref pem) { _cert_chain_pem = pem; return *this; }
Builder& private_key_pem(vespalib::stringref pem) { _private_key_pem = pem; return *this; }
Builder& authorized_peers(AuthorizedPeers auth) { _authorized_peers = std::move(auth); return *this; }
+ Builder& accepted_ciphers(std::vector<vespalib::string> ciphers) {
+ _accepted_ciphers = std::move(ciphers);
+ return *this;
+ }
};
explicit TransportSecurityOptions(Builder builder);
@@ -49,6 +58,7 @@ public:
TransportSecurityOptions copy_without_private_key() const {
return TransportSecurityOptions(_ca_certs_pem, _cert_chain_pem, "", _authorized_peers);
}
+ const std::vector<vespalib::string>& accepted_ciphers() const noexcept { return _accepted_ciphers; }
};
} // vespalib::net::tls
diff --git a/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp b/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp
index 1b6398153ca..0c29932699e 100644
--- a/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp
+++ b/vespalib/src/vespa/vespalib/net/tls/transport_security_options_reading.cpp
@@ -94,6 +94,17 @@ AuthorizedPeers parse_authorized_peers(const Inspector& authorized_peers) {
return AuthorizedPeers(std::move(policies));
}
+std::vector<vespalib::string> parse_accepted_ciphers(const Cursor& accepted_ciphers) {
+ if (!accepted_ciphers.valid()) {
+ return {};
+ }
+ std::vector<vespalib::string> ciphers;
+ for (size_t i = 0; i < accepted_ciphers.children(); ++i) {
+ ciphers.emplace_back(accepted_ciphers[i].asString().make_string());
+ }
+ return ciphers;
+}
+
std::unique_ptr<TransportSecurityOptions> load_from_input(Input& input) {
Slime root;
auto parsed = slime::JsonFormat::decode(input, root);
@@ -111,9 +122,15 @@ std::unique_ptr<TransportSecurityOptions> load_from_input(Input& input) {
auto certs = load_file_referenced_by_field(files, "certificates");
auto priv_key = load_file_referenced_by_field(files, "private-key");
auto authorized_peers = parse_authorized_peers(root["authorized-peers"]);
-
- return std::make_unique<TransportSecurityOptions>(std::move(ca_certs), std::move(certs),
- std::move(priv_key), std::move(authorized_peers));
+ auto accepted_ciphers = parse_accepted_ciphers(root["accepted-ciphers"]);
+
+ return std::make_unique<TransportSecurityOptions>(
+ TransportSecurityOptions::Builder()
+ .ca_certs_pem(ca_certs)
+ .cert_chain_pem(certs)
+ .private_key_pem(priv_key)
+ .authorized_peers(std::move(authorized_peers))
+ .accepted_ciphers(std::move(accepted_ciphers)));
}
} // anon ns