diff options
author | Jon Marius Venstad <jonmv@users.noreply.github.com> | 2018-09-05 11:38:44 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-05 11:38:44 +0200 |
commit | 68588fe72da41151f7879480a5dda4dfd81f278f (patch) | |
tree | 229a4317322e31b094b3d7ada7af71f9d320a7af | |
parent | 5a342ba113d730d320c4f929323c1a7f8cc9982d (diff) |
Revert "Add initial OpenSSL CryptoEngine implementation and key/cert handling"
19 files changed, 0 insertions, 1229 deletions
diff --git a/vespalib/CMakeLists.txt b/vespalib/CMakeLists.txt index a4b3f1e643c..33553da9422 100644 --- a/vespalib/CMakeLists.txt +++ b/vespalib/CMakeLists.txt @@ -56,7 +56,6 @@ vespa_define_module( src/tests/net/send_fd src/tests/net/socket src/tests/net/socket_spec - src/tests/net/tls/openssl_impl src/tests/objects/nbostream src/tests/optimized src/tests/printable @@ -119,8 +118,6 @@ vespa_define_module( src/vespa/vespalib/io src/vespa/vespalib/locale src/vespa/vespalib/net - src/vespa/vespalib/net/tls - src/vespa/vespalib/net/tls/impl src/vespa/vespalib/objects src/vespa/vespalib/stllike src/vespa/vespalib/test diff --git a/vespalib/src/tests/net/tls/openssl_impl/CMakeLists.txt b/vespalib/src/tests/net/tls/openssl_impl/CMakeLists.txt deleted file mode 100644 index 799e2291d7c..00000000000 --- a/vespalib/src/tests/net/tls/openssl_impl/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_executable(vespalib_net_tls_openssl_impl_test_app TEST - SOURCES - openssl_impl_test.cpp - DEPENDS - vespalib -) -vespa_add_test(NAME vespalib_net_tls_openssl_impl_test_app COMMAND vespalib_net_tls_openssl_impl_test_app) - diff --git a/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp b/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp deleted file mode 100644 index cba88f2ba56..00000000000 --- a/vespalib/src/tests/net/tls/openssl_impl/openssl_impl_test.cpp +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include <vespa/vespalib/testkit/test_kit.h> -#include <vespa/vespalib/net/tls/tls_context.h> -#include <vespa/vespalib/net/tls/transport_security_options.h> -#include <vespa/vespalib/net/tls/crypto_codec.h> -#include <iostream> -#include <stdlib.h> - -using namespace vespalib; -using namespace vespalib::net::tls; - -/* - * Generated with the following commands: - * - * openssl ecparam -name prime256v1 -genkey -out ca.key - * - * openssl req -new -x509 -nodes -key ca.key \ - * -sha256 -out ca.pem \ - * -subj '/C=US/L=LooneyVille/O=ACME/OU=ACME test CA/CN=acme.example.com' \ - * -days 10000 - * - * openssl ecparam -name prime256v1 -genkey -out host.key - * - * openssl req -new -key host.key -out host.csr \ - * -subj '/C=US/L=LooneyVille/O=Wile. E. Coyote, Ltd./CN=wile.example.com' \ - * -sha256 - * - * openssl x509 -req -in host.csr \ - * -CA ca.pem \ - * -CAkey ca.key \ - * -CAcreateserial \ - * -out host.pem \ - * -days 10000 \ - * -sha256 - * - * TODO generate keypairs and certs at test-time to avoid any hard-coding - * There certs are valid until 2046, so that buys us some time..! - */ - -// ca.pem -constexpr const char* ca_pem = R"(-----BEGIN CERTIFICATE----- -MIIBuDCCAV4CCQDpVjQIixTxvDAKBggqhkjOPQQDAjBkMQswCQYDVQQGEwJVUzEU -MBIGA1UEBwwLTG9vbmV5VmlsbGUxDTALBgNVBAoMBEFDTUUxFTATBgNVBAsMDEFD -TUUgdGVzdCBDQTEZMBcGA1UEAwwQYWNtZS5leGFtcGxlLmNvbTAeFw0xODA4MzEx -MDU3NDVaFw00NjAxMTYxMDU3NDVaMGQxCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtM -b29uZXlWaWxsZTENMAsGA1UECgwEQUNNRTEVMBMGA1UECwwMQUNNRSB0ZXN0IENB -MRkwFwYDVQQDDBBhY21lLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D -AQcDQgAE1L7IzCN5pbyVnBATIHieuxq+hf9kWyn5yfjkXMhD52T5ITz1huq4nbiN -YtRoRP7XmipI60R/uiCHzERcsVz4rDAKBggqhkjOPQQDAgNIADBFAiEA6wmZDBca -y0aJ6ABtjbjx/vlmVDxdkaSZSgO8h2CkvIECIFktCkbZhDFfSvbqUScPOGuwkdGQ -L/EW2Bxp+1BPcYoZ ------END CERTIFICATE-----)"; - -// host.pem -constexpr const char* cert_pem = R"(-----BEGIN CERTIFICATE----- -MIIBsTCCAVgCCQD6GfDh0ltpsjAKBggqhkjOPQQDAjBkMQswCQYDVQQGEwJVUzEU -MBIGA1UEBwwLTG9vbmV5VmlsbGUxDTALBgNVBAoMBEFDTUUxFTATBgNVBAsMDEFD -TUUgdGVzdCBDQTEZMBcGA1UEAwwQYWNtZS5leGFtcGxlLmNvbTAeFw0xODA4MzEx -MDU3NDVaFw00NjAxMTYxMDU3NDVaMF4xCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtM -b29uZXlWaWxsZTEeMBwGA1UECgwVV2lsZS4gRS4gQ295b3RlLCBMdGQuMRkwFwYD -VQQDDBB3aWxlLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE -e+Y4hxt66em0STviGUj6ZDbxzoLoubXWRml8JDFrEc2S2433KWw2npxYSKVCyo3a -/Vo33V8/H0WgOXioKEZJxDAKBggqhkjOPQQDAgNHADBEAiAN+87hQuGv3z0Ja2BV -b8PHq2vp3BJHjeMuxWu4BFPn0QIgYlvIHikspgGatXRNMZ1gPC0oCccsJFcie+Cw -zL06UPI= ------END CERTIFICATE-----)"; - -// host.key -constexpr const char* key_pem = R"(-----BEGIN EC PARAMETERS----- -BggqhkjOPQMBBw== ------END EC PARAMETERS----- ------BEGIN EC PRIVATE KEY----- -MHcCAQEEID6di2PFYn8hPrxPbkFDGkSqF+K8L520In7nx3g0jwzOoAoGCCqGSM49 -AwEHoUQDQgAEe+Y4hxt66em0STviGUj6ZDbxzoLoubXWRml8JDFrEc2S2433KWw2 -npxYSKVCyo3a/Vo33V8/H0WgOXioKEZJxA== ------END EC PRIVATE KEY-----)"; - -const char* decode_state_to_str(DecodeResult::State state) noexcept { - switch (state) { - case DecodeResult::State::Failed: return "Broken"; - case DecodeResult::State::OK: return "OK"; - case DecodeResult::State::NeedsMorePeerData: return "NeedsMorePeerData"; - default: - abort(); - } -} - -const char* hs_state_to_str(HandshakeResult::State state) noexcept { - switch (state) { - case HandshakeResult::State::Failed: return "Broken"; - case HandshakeResult::State::Done: return "Done"; - case HandshakeResult::State::NeedsMorePeerData: return "NeedsMorePeerData"; - default: - abort(); - } -} - -void log_handshake_result(const char* mode, const HandshakeResult& res) { - fprintf(stderr, "(handshake) %s consumed %zu peer bytes, wrote %zu peer bytes. State: %s\n", - mode, res.bytes_consumed, res.bytes_produced, - hs_state_to_str(res.state)); -} - -void log_encode_result(const char* mode, const EncodeResult& res) { - fprintf(stderr, "(encode) %s read %zu plaintext, wrote %zu cipher. State: %s\n", - mode, res.bytes_consumed, res.bytes_produced, - res.failed ? "Broken! D:" : "OK"); -} - -void log_decode_result(const char* mode, const DecodeResult& res) { - fprintf(stderr, "(decode) %s read %zu cipher, wrote %zu plaintext. State: %s\n", - mode, res.bytes_consumed, res.bytes_produced, - decode_state_to_str(res.state)); -} - -bool complete_handshake(CryptoCodec& client, CryptoCodec& server) { - // Not using vespalib::string here since it doesn't have erase(iter, length) implemented. - std::string client_to_server_buf; - std::string server_to_client_buf; - - HandshakeResult cli_res; - HandshakeResult serv_res; - while (!(cli_res.done() && serv_res.done())) { - client_to_server_buf.resize(client.min_encode_buffer_size()); - server_to_client_buf.resize(server.min_encode_buffer_size()); - - cli_res = client.handshake(server_to_client_buf.data(), serv_res.bytes_produced, - client_to_server_buf.data(), client_to_server_buf.size()); - log_handshake_result("client", cli_res); - server_to_client_buf.erase(server_to_client_buf.begin(), server_to_client_buf.begin() + cli_res.bytes_consumed); - - serv_res = server.handshake(client_to_server_buf.data(), cli_res.bytes_produced, - server_to_client_buf.data(), server_to_client_buf.size()); - log_handshake_result("server", serv_res); - client_to_server_buf.erase(client_to_server_buf.begin(), client_to_server_buf.begin() + serv_res.bytes_consumed); - - if (cli_res.failed() || serv_res.failed()) { - return false; - } - } - return true; -} - -TEST("client and server can complete handshake") { - // TODO move to fixture - auto tls_opts = TransportSecurityOptions(ca_pem, cert_pem, key_pem); - auto tls_ctx = TlsContext::create_default_context(tls_opts); - auto client = CryptoCodec::create_default_codec(*tls_ctx, CryptoCodec::Mode::Client); - auto server = CryptoCodec::create_default_codec(*tls_ctx, CryptoCodec::Mode::Server); - - EXPECT_TRUE(complete_handshake(*client, *server)); -} - -TEST("client can send single data frame to server after handshake") { - // TODO move to fixture - auto tls_opts = TransportSecurityOptions(ca_pem, cert_pem, key_pem); - auto tls_ctx = TlsContext::create_default_context(tls_opts); - auto client = CryptoCodec::create_default_codec(*tls_ctx, CryptoCodec::Mode::Client); - auto server = CryptoCodec::create_default_codec(*tls_ctx, CryptoCodec::Mode::Server); - - ASSERT_TRUE(complete_handshake(*client, *server)); - - std::string client_to_server_buf; - client_to_server_buf.resize(client->min_encode_buffer_size()); - - std::string client_plaintext = "Hellooo world! :D"; - auto cli_res = client->encode(client_plaintext.data(), client_plaintext.size(), - client_to_server_buf.data(), client_to_server_buf.size()); - log_encode_result("client", cli_res); - - std::string server_plaintext_out; - server_plaintext_out.resize(server->min_decode_buffer_size()); - auto serv_res = server->decode(client_to_server_buf.data(), cli_res.bytes_produced, - server_plaintext_out.data(), server_plaintext_out.size()); - log_decode_result("server", serv_res); - - ASSERT_FALSE(cli_res.failed); - ASSERT_FALSE(serv_res.failed()); - - ASSERT_TRUE(serv_res.state == DecodeResult::State::OK); - std::string data_received(server_plaintext_out.data(), serv_res.bytes_produced); - EXPECT_EQUAL(client_plaintext, data_received); -} - -/* - * TODO tests: - * - full duplex read/write - * - read and write of > frame size data - * - handshakes with multi frame writes - * - completed handshake with pipelined data frame - * - short ciphertext reads on decode - * - short plaintext writes on decode (.. if we even want to support this..) - * - short ciphertext write on encode - * - peer certificate validation on server - * - peer certificate validation on client - * - detection of peer shutdown session - */ - -TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/vespalib/src/vespa/vespalib/CMakeLists.txt b/vespalib/src/vespa/vespalib/CMakeLists.txt index dadfdec49d7..480caf8f28d 100644 --- a/vespalib/src/vespa/vespalib/CMakeLists.txt +++ b/vespalib/src/vespa/vespalib/CMakeLists.txt @@ -9,8 +9,6 @@ vespa_add_library(vespalib $<TARGET_OBJECTS:vespalib_vespalib_io> $<TARGET_OBJECTS:vespalib_vespalib_locale> $<TARGET_OBJECTS:vespalib_vespalib_net> - $<TARGET_OBJECTS:vespalib_vespalib_net_tls> - $<TARGET_OBJECTS:vespalib_vespalib_net_tls_impl> $<TARGET_OBJECTS:vespalib_vespalib_objects> $<TARGET_OBJECTS:vespalib_vespalib_stllike> $<TARGET_OBJECTS:vespalib_vespalib_testkit> @@ -25,5 +23,3 @@ vespa_add_library(vespalib vespalib_vespalib_test gcc ) - -vespa_add_target_package_dependency(vespalib OpenSSL) diff --git a/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt b/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt deleted file mode 100644 index 6f0e88d3e39..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_library(vespalib_vespalib_net_tls OBJECT - SOURCES - crypto_codec.cpp - crypto_exception.cpp - tls_context.cpp - transport_security_options.cpp - DEPENDS -) -vespa_add_target_package_dependency(vespalib_vespalib_net_tls OpenSSL) - diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_codec.cpp b/vespalib/src/vespa/vespalib/net/tls/crypto_codec.cpp deleted file mode 100644 index b36913d20e3..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/crypto_codec.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include "crypto_codec.h" -#include <vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h> -#include <vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h> -#include <cassert> - -namespace vespalib::net::tls { - -std::unique_ptr<CryptoCodec> CryptoCodec::create_default_codec(TlsContext& ctx, Mode mode) { - auto* ssl_ctx = dynamic_cast<impl::OpenSslTlsContextImpl*>(&ctx); - assert(ssl_ctx != nullptr); - return std::make_unique<impl::OpenSslCryptoCodecImpl>(*ssl_ctx->native_context(), mode); -} - -} diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_codec.h b/vespalib/src/vespa/vespalib/net/tls/crypto_codec.h deleted file mode 100644 index 6e690c809a5..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/crypto_codec.h +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#pragma once - -#include <memory> - -namespace vespalib::net::tls { - -struct HandshakeResult { - // Handshake bytes consumed from peer. - size_t bytes_consumed = 0; - // Handshake bytes produced that must be sent to the peer. - size_t bytes_produced = 0; - enum class State { - Failed, - Done, - NeedsMorePeerData - }; - State state = State::Failed; - - bool failed() const noexcept { return (state == State::Failed); } - bool done() const noexcept { return (state == State::Done); } -}; - -struct EncodeResult { - // Plaintext bytes consumed - size_t bytes_consumed = 0; - // Ciphertext bytes produced that must be sent to the peer - size_t bytes_produced = 0; - bool failed = true; -}; - -struct DecodeResult { - // Ciphertext bytes consumed from peer - size_t bytes_consumed = 0; - // Plaintext bytes produced. - size_t bytes_produced = 0; - enum class State { - Failed, - OK, - NeedsMorePeerData - // TODO add Closed/Shutdown as own state? - }; - State state = State::Failed; - - bool failed() const noexcept { return (state == State::Failed); } -}; - -class TlsContext; - -// TODO move to different namespace, not dependent on TLS? - -/* - * A CryptoCodec provides a fully transport-independent way of negotiating - * a secure, authenticated session towards another peer. The codec requires - * the caller to handle any and all actual data transfer - */ -class CryptoCodec { -public: - enum class Mode { - Client, Server - }; - - virtual ~CryptoCodec() = default; - - /* - * Minimum buffer size required to represent one wire format frame - * of encrypted (ciphertext) data, including frame overhead. - */ - virtual size_t min_encode_buffer_size() const noexcept = 0; - /* - * Minimum buffer size required to represent the decoded (plaintext) - * output of a single frame of encrypted data. - */ - virtual size_t min_decode_buffer_size() const noexcept = 0; - - /* - * Precondition: to_peer_buf_size >= min_encode_buffer_size() - * Postcondition: if result.done(), the handshake process has completed - * and data may be passed through encode()/decode(). - */ - virtual HandshakeResult handshake(const char* from_peer, size_t from_peer_buf_size, - char* to_peer, size_t to_peer_buf_size) noexcept = 0; - - /* - * Encodes a single ciphertext frame into `ciphertext`. If plaintext_size - * is greater than can fit into a frame, the returned result's consumed_bytes - * field will be < plaintext_size. The number of actual ciphertext bytes produced - * is available in the returned result's produced_bytes field. - * - * Precondition: handshake must be completed - * Precondition: ciphertext_size >= min_encode_buffer_size(), i.e. it must be - * possible to encode at least 1 frame. - * Postcondition: if plaintext_size > 0 and result.failed == false, a single - * frame of ciphertext has been written into the to_peer buffer. - * Size of written frame is given by result.bytes_produced. This - * includes all protocol-specific frame overhead. - */ - virtual EncodeResult encode(const char* plaintext, size_t plaintext_size, - char* ciphertext, size_t ciphertext_size) noexcept = 0; - /* - * Attempt to decode ciphertext sent by the peer into plaintext. Since - * ciphertext is sent in frames, it's possible that invoking decode() - * may produce a CodecResult with a state of `NeedsMorePeerData` if a - * complete frame is not present in `ciphertext`. In this case, decode() - * must be called again once more data is available. - * - * Precondition: handshake must be completed - * Precondition: plaintext_size >= min_decode_buffer_size() - * Postcondition: if result.state == DecodeResult::State::OK, at least 1 - * complete frame has been written to the `plaintext` buffer - */ - virtual DecodeResult decode(const char* ciphertext, size_t ciphertext_size, - char* plaintext, size_t plaintext_size) noexcept = 0; - - /* - * Creates an implementation defined CryptoCodec that provides at least TLSv1.2 - * compliant handshaking and full duplex data transfer. - * - * Throws CryptoException if resources cannot be allocated for the codec. - */ - static std::unique_ptr<CryptoCodec> create_default_codec(TlsContext& ctx, Mode mode); -}; - -} diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_exception.cpp b/vespalib/src/vespa/vespalib/net/tls/crypto_exception.cpp deleted file mode 100644 index 41bb2060c04..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/crypto_exception.cpp +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "crypto_exception.h" - -namespace vespalib::net::tls { - -VESPA_IMPLEMENT_EXCEPTION(CryptoException, Exception); - -} - diff --git a/vespalib/src/vespa/vespalib/net/tls/crypto_exception.h b/vespalib/src/vespa/vespalib/net/tls/crypto_exception.h deleted file mode 100644 index 696a158e058..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/crypto_exception.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#pragma once - -#include <vespa/vespalib/util/exception.h> - -namespace vespalib::net::tls { - -VESPA_DEFINE_EXCEPTION(CryptoException, Exception); - -} diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/CMakeLists.txt b/vespalib/src/vespa/vespalib/net/tls/impl/CMakeLists.txt deleted file mode 100644 index 8b4398679db..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/impl/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -vespa_add_library(vespalib_vespalib_net_tls_impl OBJECT - SOURCES - openssl_tls_context_impl.cpp - openssl_crypto_codec_impl.cpp - DEPENDS -) -vespa_add_target_package_dependency(vespalib_vespalib_net_tls_impl OpenSSL) - diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp deleted file mode 100644 index 13e1be2ce34..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.cpp +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include "openssl_crypto_codec_impl.h" -#include "openssl_tls_context_impl.h" -#include <vespa/vespalib/net/tls/crypto_codec.h> -#include <vespa/vespalib/net/tls/crypto_exception.h> -#include <mutex> -#include <vector> -#include <memory> -#include <stdexcept> -#include <openssl/ssl.h> -#include <openssl/crypto.h> -#include <openssl/err.h> -#include <openssl/pem.h> - -#include <vespa/log/log.h> -LOG_SETUP(".vespalib.net.tls.openssl_crypto_codec_impl"); - -#if (OPENSSL_VERSION_NUMBER < 0x10000000L) -// < 1.0 requires explicit thread ID callback support. -# error "Provided OpenSSL version is too darn old, need at least 1.0" -#endif - -/* - * Beware all ye who dare enter, for this is OpenSSL integration territory. - * Dragons are known to roam the skies. Strange whispers are heard at night - * in the mist-covered lands where the forest meets the lake. Rumors of a - * tome that contains best practices and excellent documentation are heard - * at the local inn, but no one seems to know where it exists, or even if - * it ever existed. Be it best that people carry on with their lives and - * pretend to not know of the beasts that lurk beyond where the torch's - * light fades and turns to all-enveloping darkness. - */ - -namespace vespalib::net::tls::impl { - -namespace { - -const char* ssl_error_to_str(int ssl_error) noexcept { - // From https://www.openssl.org/docs/manmaster/man3/SSL_get_error.html - // Our code paths shouldn't trigger most of these, but included for completeness - switch (ssl_error) { - case SSL_ERROR_NONE: - return "SSL_ERROR_NONE"; - case SSL_ERROR_ZERO_RETURN: - return "SSL_ERROR_ZERO_RETURN"; - case SSL_ERROR_WANT_READ: - return "SSL_ERROR_WANT_READ"; - case SSL_ERROR_WANT_WRITE: - return "SSL_ERROR_WANT_WRITE"; - case SSL_ERROR_WANT_CONNECT: - return "SSL_ERROR_WANT_CONNECT"; - case SSL_ERROR_WANT_ACCEPT: - return "SSL_ERROR_WANT_ACCEPT"; - case SSL_ERROR_WANT_X509_LOOKUP: - return "SSL_ERROR_WANT_X509_LOOKUP"; -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) - case SSL_ERROR_WANT_ASYNC: - return "SSL_ERROR_WANT_ASYNC"; - case SSL_ERROR_WANT_ASYNC_JOB: - return "SSL_ERROR_WANT_ASYNC_JOB"; -#endif -#if (OPENSSL_VERSION_NUMBER >= 0x10101000L) - case SSL_ERROR_WANT_CLIENT_HELLO_CB: - return "SSL_ERROR_WANT_CLIENT_HELLO_CB"; -#endif - case SSL_ERROR_SYSCALL: - return "SSL_ERROR_SYSCALL"; - case SSL_ERROR_SSL: - return "SSL_ERROR_SSL"; - default: - return "Unknown SSL error code"; - } -} - -HandshakeResult handshake_consumed_bytes_and_needs_more_peer_data(size_t consumed) noexcept { - return {consumed, 0, HandshakeResult::State::NeedsMorePeerData}; -} - -HandshakeResult handshake_produced_bytes_and_needs_more_peer_data(size_t produced) noexcept { - return {0, produced, HandshakeResult::State::NeedsMorePeerData}; -} - -HandshakeResult handshake_consumed_bytes_and_is_complete(size_t consumed) noexcept { - return {consumed, 0, HandshakeResult::State::Done}; -} - -HandshakeResult handshaked_bytes(size_t consumed, size_t produced, HandshakeResult::State state) noexcept { - return {consumed, produced, state}; -} - -HandshakeResult handshake_completed() noexcept { - return {0, 0, HandshakeResult::State::Done}; -} - -HandshakeResult handshake_failed() noexcept { - return {0, 0, HandshakeResult::State::Failed}; -} - -EncodeResult encode_failed() noexcept { - return {0, 0, true}; -} - -EncodeResult encoded_bytes(size_t consumed, size_t produced) noexcept { - return {consumed, produced, false}; -} - -DecodeResult decode_failed() noexcept { - return {0, 0, DecodeResult::State::Failed}; -} - -DecodeResult decoded_frames_with_plaintext_bytes(size_t produced_bytes) noexcept { - return {0, produced_bytes, DecodeResult::State::OK}; -} - -DecodeResult decode_needs_more_peer_data() noexcept { - return {0, 0, DecodeResult::State::NeedsMorePeerData}; -} - -DecodeResult decoded_bytes(size_t consumed, size_t produced, DecodeResult::State state) noexcept { - return {consumed, produced, state}; -} - -BioPtr new_tls_frame_memory_bio() { - BioPtr bio(::BIO_new(BIO_s_mem())); - if (!bio) { - throw CryptoException("IO_new(BIO_s_mem()) failed; out of memory?"); - } - BIO_set_write_buf_size(bio.get(), 0); // 0 ==> default max frame size - return bio; -} - -} // anon ns - -OpenSslCryptoCodecImpl::OpenSslCryptoCodecImpl(::SSL_CTX& ctx, Mode mode) - : _ssl(::SSL_new(&ctx)), - _mode(mode) -{ - if (!_ssl) { - throw CryptoException("Failed to create new SSL from SSL_CTX"); - } - /* - * We use two separate memory BIOs rather than a BIO pair for writing and - * reading ciphertext, respectively. This is because it _seems_ quite - * a bit more straight forward to implement a full duplex API with two - * separate BIOs, but there is little available documentation as to the - * 'hows' and 'whys' around this. - * There are claims from core OpenSSL devs[0] that BIO pairs are more efficient, - * so we may reconsider the current approach (or just use the "OpenSSL controls - * the file descriptor" yolo approach for simplicity, assuming they do optimal - * stuff internally). - * - * Our BIOs are used as follows: - * - * Handshakes may use both BIOs opaquely: - * - * handshake() : SSL_do_handshake() --(_output_bio ciphertext)--> BIO_read --> [peer] - * : SSL_do_handshake() <--(_input_bio ciphertext)-- BIO_write <-- [peer] - * - * Once handshaking is complete, the input BIO is only used for decodes and the output - * BIO is only used for encodes. We explicitly disallow TLS renegotiation, both for - * the sake of simplicity and for added security (renegotiation is a bit of a rat's nest). - * - * encode() : SSL_write(plaintext) --(_output_bio ciphertext)--> BIO_read --> [peer] - * decode() : SSL_read(plaintext) <--(_input_bio ciphertext)-- BIO_write <-- [peer] - * - * To avoid blowing the sizes of BIOs out of the water, we do our best to encode and decode - * on a per-TLS frame granularity (16K) maximum. - */ - BioPtr tmp_input_bio = new_tls_frame_memory_bio(); - BioPtr tmp_output_bio = new_tls_frame_memory_bio(); - // Connect BIOs used internally by OpenSSL. This transfers ownership. No return value to check. - // TODO replace with explicit SSL_set0_rbio/SSL_set0_wbio on OpenSSL >= v1.1 - ::SSL_set_bio(_ssl.get(), tmp_input_bio.get(), tmp_output_bio.get()); - _input_bio = tmp_input_bio.release(); - _output_bio = tmp_output_bio.release(); - if (_mode == Mode::Client) { - ::SSL_set_connect_state(_ssl.get()); - } else { - ::SSL_set_accept_state(_ssl.get()); - } -} - -// TODO remove spammy logging once code is stable - -// Produces bytes previously written to _output_bio by SSL_do_handshake or SSL_write -int OpenSslCryptoCodecImpl::drain_outgoing_network_bytes_if_any( - char *to_peer, size_t to_peer_buf_size) noexcept { - int out_pending = BIO_pending(_output_bio); - if (out_pending > 0) { - int copied = ::BIO_read(_output_bio, to_peer, static_cast<int>(to_peer_buf_size)); - // TODO BIO_should_retry here? Semantics are unclear, especially for memory BIOs. - LOG(spam, "BIO_read copied out %d bytes of ciphertext from _output_bio", copied); - if (copied < 0) { - LOG(error, "Memory BIO_read() failed with BIO_pending() > 0"); - } - return copied; - } - return out_pending; -} - -HandshakeResult OpenSslCryptoCodecImpl::handshake(const char* from_peer, size_t from_peer_buf_size, - char* to_peer, size_t to_peer_buf_size) noexcept { - LOG_ASSERT(from_peer != nullptr && to_peer != nullptr - && from_peer_buf_size < INT32_MAX && to_peer_buf_size < INT32_MAX); - - if (SSL_is_init_finished(_ssl.get())) { - return handshake_completed(); - } - // Still ciphertext data left? If so, get rid of it before we start a new operation - // that wants to fill the output BIO. - int produced = drain_outgoing_network_bytes_if_any(to_peer, to_peer_buf_size); - if (produced > 0) { - // Handshake isn't complete yet and we've got stuff to send. Need to continue handshake - // once more data is available from the peer. - return handshake_produced_bytes_and_needs_more_peer_data(static_cast<size_t>(produced)); - } else if (produced < 0) { - return handshake_failed(); - } - const auto consume_res = do_handshake_and_consume_peer_input_bytes(from_peer, from_peer_buf_size); - LOG_ASSERT(consume_res.bytes_produced == 0); - if (consume_res.failed()) { - return consume_res; - } - // SSL_do_handshake() might have produced more data to send. Note: handshake may - // be complete at this point. - produced = drain_outgoing_network_bytes_if_any(to_peer, to_peer_buf_size); - if (produced < 0) { - return handshake_failed(); - } - return handshaked_bytes(consume_res.bytes_consumed, static_cast<size_t>(produced), consume_res.state); -} - -HandshakeResult OpenSslCryptoCodecImpl::do_handshake_and_consume_peer_input_bytes( - const char *from_peer, size_t from_peer_buf_size) noexcept { - // Feed the SSL session input in frame-sized chunks between each call to SSL_do_handshake(). - // This is primarily to ensure we don't shove unbounded amounts of data into the BIO - // in the case that someone naughty is sending us tons of garbage over the socket. - size_t consumed_total = 0; - while (true) { - // Assumption: SSL_do_handshake will place all required outgoing handshake - // data in the output memory BIO without requiring WANT_WRITE. Freestanding - // memory BIOs are _supposedly_ auto-resizing, so this should work transparently. - // At the very least, if this is not the case we'll auto-fail the connection - // and quickly find out..! - // TODO test multi-frame sized handshake - // TODO should we invoke ::ERR_clear_error() prior? - int ssl_result = ::SSL_do_handshake(_ssl.get()); - ssl_result = ::SSL_get_error(_ssl.get(), ssl_result); - - if (ssl_result == SSL_ERROR_WANT_READ) { - LOG(spam, "SSL_do_handshake() returned SSL_ERROR_WANT_READ"); - if (from_peer_buf_size - consumed_total > 0) { - int consumed = ::BIO_write(_input_bio, from_peer + consumed_total, - static_cast<int>(std::min(MaximumTlsFrameSize, from_peer_buf_size - consumed_total))); - LOG(spam, "BIO_write copied in %d bytes of ciphertext to _input_bio", consumed); - if (consumed < 0) { - LOG(error, "Memory BIO_write() returned %d", consumed); // TODO BIO_need_retry? - return handshake_failed(); - } - consumed_total += consumed; // TODO protect against consumed == 0? - continue; - } else { - return handshake_consumed_bytes_and_needs_more_peer_data(consumed_total); - } - } else if (ssl_result == SSL_ERROR_NONE) { - // At this point SSL_do_handshake has stated it does not need any more peer data, i.e. - // the handshake is complete. - if (!SSL_is_init_finished(_ssl.get())) { - LOG(error, "SSL handshake is not completed even though no more peer data is requested"); - return handshake_failed(); - } - return handshake_consumed_bytes_and_is_complete(consumed_total); - } else { - LOG(error, "SSL_do_handshake() returned unexpected error: %s", ssl_error_to_str(ssl_result)); - return handshake_failed(); - } - }; -} - -EncodeResult OpenSslCryptoCodecImpl::encode(const char* plaintext, size_t plaintext_size, - char* ciphertext, size_t ciphertext_size) noexcept { - LOG_ASSERT(plaintext != nullptr && ciphertext != nullptr - && plaintext_size < INT32_MAX && ciphertext_size < INT32_MAX); - - if (!SSL_is_init_finished(_ssl.get())) { - LOG(error, "OpenSslCryptoCodecImpl::encode() called before handshake completed"); - return encode_failed(); - } - size_t bytes_consumed = 0; - if (plaintext_size != 0) { - int to_consume = static_cast<int>(std::min(plaintext_size, MaximumFramePlaintextSize)); - // SSL_write encodes plaintext to ciphertext and writes to _output_bio - int consumed = ::SSL_write(_ssl.get(), plaintext, to_consume); - LOG(spam, "After SSL_write() -> %d, _input_bio pending=%d, _output_bio pending=%d", - consumed, BIO_pending(_input_bio), BIO_pending(_output_bio)); - if (consumed < 0) { - int ssl_error = ::SSL_get_error(_ssl.get(), consumed); - LOG(error, "SSL_write() failed to write frame, got error %s", ssl_error_to_str(ssl_error)); - // TODO explicitly detect and log TLS renegotiation error (SSL_ERROR_WANT_READ)? - return encode_failed(); - } else if (consumed != to_consume) { - LOG(error, "SSL_write() returned OK but did not consume all requested plaintext"); - return encode_failed(); - } - bytes_consumed = static_cast<size_t>(consumed); - } - - int produced = drain_outgoing_network_bytes_if_any(ciphertext, ciphertext_size); - if (produced < 0) { - return encode_failed(); - } - if (BIO_pending(_output_bio) != 0) { - LOG(error, "Residual data left in output BIO on encode(); provided buffer is too small"); - return encode_failed(); - } - return encoded_bytes(bytes_consumed, static_cast<size_t>(produced)); -} -DecodeResult OpenSslCryptoCodecImpl::decode(const char* ciphertext, size_t ciphertext_size, - char* plaintext, size_t plaintext_size) noexcept { - LOG_ASSERT(ciphertext != nullptr && plaintext != nullptr - && ciphertext_size < INT32_MAX && plaintext_size < INT32_MAX); - - if (!SSL_is_init_finished(_ssl.get())) { - LOG(error, "OpenSslCryptoCodecImpl::decode() called before handshake completed"); - return decode_failed(); - } - auto produce_res = drain_and_produce_plaintext_from_ssl(plaintext, static_cast<int>(plaintext_size)); - if ((produce_res.bytes_produced > 0) || produce_res.failed()) { - return produce_res; // TODO gRPC [1] handles this differently... allows fallthrough - } - int consumed = consume_peer_input_bytes(ciphertext, ciphertext_size); - if (consumed < 0) { - return decode_failed(); - } - produce_res = drain_and_produce_plaintext_from_ssl(plaintext, static_cast<int>(plaintext_size)); - return decoded_bytes(static_cast<size_t>(consumed), produce_res.bytes_produced, produce_res.state); -} - -DecodeResult OpenSslCryptoCodecImpl::drain_and_produce_plaintext_from_ssl( - char* plaintext, size_t plaintext_size) noexcept { - // SSL_read() is named a bit confusingly. We read _from_ the SSL-internal state - // via the input BIO _into_ to the receiving plaintext buffer. - // This may consume the entire, parts of, or none of the input BIO's data, - // depending on how much TLS frame data is available and its size relative - // to the receiving plaintext buffer. - int produced = ::SSL_read(_ssl.get(), plaintext, static_cast<int>(plaintext_size)); - LOG(spam, "After SSL_read() -> %d, _input_bio pending=%d, _output_bio pending=%d", - produced, BIO_pending(_input_bio), BIO_pending(_output_bio)); - if (produced > 0) { - // At least 1 frame decoded successfully. - return decoded_frames_with_plaintext_bytes(static_cast<size_t>(produced)); - } else { - int ssl_error = ::SSL_get_error(_ssl.get(), produced); - switch (ssl_error) { - case SSL_ERROR_WANT_READ: - // SSL_read() was not able to decode a full frame with the ciphertext that - // we've fed it thus far; caller must feed it some and then try again. - LOG(spam, "SSL_read() returned SSL_ERROR_WANT_READ, must get more ciphertext"); - return decode_needs_more_peer_data(); - default: - LOG(error, "SSL_read() returned unexpected error: %s", ssl_error_to_str(ssl_error)); - return decode_failed(); - } - } -} - -int OpenSslCryptoCodecImpl::consume_peer_input_bytes( - const char* ciphertext, size_t ciphertext_size) noexcept { - // TODO BIO_need_retry on failure? Can this even happen for memory BIOs? - int consumed = ::BIO_write(_input_bio, ciphertext, static_cast<int>(std::min(MaximumTlsFrameSize, ciphertext_size))); - LOG(spam, "BIO_write copied in %d bytes of ciphertext to _input_bio", consumed); - if (consumed < 0) { - LOG(error, "Memory BIO_write() returned %d", consumed); - } - return consumed; -} - -} - -// External references: -// [0] http://openssl.6102.n7.nabble.com/nonblocking-implementation-question-tp1728p1732.html -// [1] https://github.com/grpc/grpc/blob/master/src/core/tsi/ssl_transport_security.cc diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h deleted file mode 100644 index 44ca8859596..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_crypto_codec_impl.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#pragma once - -#include "openssl_typedefs.h" -#include <vespa/vespalib/net/tls/transport_security_options.h> -#include <vespa/vespalib/net/tls/crypto_codec.h> -#include <memory> - -namespace vespalib::net::tls { class TlsContext; } - -namespace vespalib::net::tls::impl { - -/* - * Frame-level OpenSSL-backed TLSv1.2 crypto codec implementation. - * - * Currently has sub-optimal buffer management, and is mostly intended - * as a starting point. - * - * NOT thread safe per instance, but independent instances may be - * used by different threads safely. - */ -class OpenSslCryptoCodecImpl : public CryptoCodec { - SslPtr _ssl; - ::BIO* _input_bio; // Owned by _ssl - ::BIO* _output_bio; // Owned by _ssl - Mode _mode; -public: - OpenSslCryptoCodecImpl(::SSL_CTX& ctx, Mode mode); - - /* - * From RFC 8449 (Record Size Limit Extension for TLS), section 1: - * "TLS versions 1.2 [RFC5246] and earlier permit senders to - * generate records 16384 octets in size, plus any expansion - * from compression and protection up to 2048 octets (though - * typically this expansion is only 16 octets). TLS 1.3 reduces - * the allowance for expansion to 256 octets." - * - * We're on TLSv1.2, so make room for the worst case. - */ - static constexpr size_t MaximumTlsFrameSize = 16384 + 2048; - static constexpr size_t MaximumFramePlaintextSize = 16384; - - size_t min_encode_buffer_size() const noexcept override { - return MaximumTlsFrameSize; - } - size_t min_decode_buffer_size() const noexcept override { - return MaximumFramePlaintextSize; - } - - HandshakeResult handshake(const char* from_peer, size_t from_peer_buf_size, - char* to_peer, size_t to_peer_buf_size) noexcept override; - - EncodeResult encode(const char* plaintext, size_t plaintext_size, - char* ciphertext, size_t ciphertext_size) noexcept override; - DecodeResult decode(const char* ciphertext, size_t ciphertext_size, - char* plaintext, size_t plaintext_size) noexcept override; -private: - /* - * Returns - * n > 0 if n bytes written to `to_peer`. Always <= to_peer_buf_size - * n == 0 if no bytes pending in output BIO - * n < 0 on error - */ - int drain_outgoing_network_bytes_if_any(char *to_peer, size_t to_peer_buf_size) noexcept; - /* - * Returns - * n > 0 if n bytes written to `ciphertext`. Always <= ciphertext_size - * n == 0 if no bytes pending in input BIO - * n < 0 on error - */ - int consume_peer_input_bytes(const char* ciphertext, size_t ciphertext_size) noexcept; - HandshakeResult do_handshake_and_consume_peer_input_bytes(const char *from_peer, size_t from_peer_buf_size) noexcept; - DecodeResult drain_and_produce_plaintext_from_ssl(char* plaintext, size_t plaintext_size) noexcept; -}; - -} 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 deleted file mode 100644 index c868f695b98..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.cpp +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include "openssl_typedefs.h" -#include "openssl_tls_context_impl.h" -#include <vespa/vespalib/net/tls/crypto_exception.h> -#include <vespa/vespalib/net/tls/transport_security_options.h> -#include <mutex> -#include <vector> -#include <memory> -#include <stdexcept> -#include <openssl/ssl.h> -#include <openssl/crypto.h> -#include <openssl/err.h> -#include <openssl/pem.h> - -#include <vespa/log/log.h> -LOG_SETUP(".vespalib.net.tls.openssl_tls_context_impl"); - -#if (OPENSSL_VERSION_NUMBER < 0x10000000L) -// < 1.0 requires explicit thread ID callback support. -# error "Provided OpenSSL version is too darn old, need at least 1.0" -#endif - -namespace vespalib::net::tls::impl { - -namespace { - -#if (OPENSSL_VERSION_NUMBER < 0x10100000L) - -std::vector<std::unique_ptr<std::mutex>> _g_mutexes; - -// Some works on OpenSSL legacy locking: OpenSSL does not implement locking -// itself internally, deferring to user code callbacks that Do The Needful(tm). -// The `n` parameter refers to the nth mutex, which is always < CRYPTO_num_locks(). -void openssl_locking_cb(int mode, int n, [[maybe_unused]] const char *file, [[maybe_unused]] int line) { - if (mode & CRYPTO_LOCK) { - _g_mutexes[n]->lock(); - } else { - _g_mutexes[n]->unlock(); - } -} - -#endif - -struct OpenSslLibraryResources { - OpenSslLibraryResources(); - ~OpenSslLibraryResources(); -}; - -OpenSslLibraryResources::OpenSslLibraryResources() { - // Other implementations (Asio, gRPC) disagree on whether main library init - // itself should take place on >= v1.1. We always do it to be on the safe side..! - ::SSL_library_init(); - ::SSL_load_error_strings(); - ::OpenSSL_add_all_algorithms(); - // Luckily, the mutex callback madness is not present on >= v1.1 -#if (OPENSSL_VERSION_NUMBER < 0x10100000L) - // Since the init path should happen only once globally, but multiple libraries - // may use OpenSSL, make sure we don't step on any toes if locking callbacks are - // already set up. - if (!::CRYPTO_get_locking_callback()) { - const int num_locks = ::CRYPTO_num_locks(); - LOG_ASSERT(num_locks > 0); - _g_mutexes.reserve(num_locks); - for (int i = 0; i < num_locks; ++i) { - _g_mutexes.emplace_back(std::make_unique<std::mutex>()); - } - ::CRYPTO_set_locking_callback(openssl_locking_cb); - } -#endif -} - -OpenSslLibraryResources::~OpenSslLibraryResources() { -#if (OPENSSL_VERSION_NUMBER < 0x10100000L) - if (::CRYPTO_get_locking_callback() == openssl_locking_cb) { - ::CRYPTO_set_locking_callback(nullptr); - } -#endif - ::ERR_free_strings(); - ::EVP_cleanup(); - ::CRYPTO_cleanup_all_ex_data(); -} - -// TODO make global init instead..? -void ensure_openssl_initialized_once() { - static OpenSslLibraryResources openssl_resources; - (void) openssl_resources; -} - -BioPtr bio_from_string(vespalib::stringref str) { - LOG_ASSERT(str.size() <= INT_MAX); - BioPtr bio(::BIO_new_mem_buf(str.data(), static_cast<int>(str.size()))); - if (!bio) { - throw CryptoException("BIO_new_mem_buf"); - } - return bio; -} - -// Several OpenSSL functions take a magical user passphrase argument with -// potentially horrible default behavior for password protected input. -// -// From OpenSSL docs (https://www.openssl.org/docs/man1.1.0/crypto/PEM_read_bio_PrivateKey.html): -// -// "If the cb parameters is set to NULL and the u parameter is not NULL -// then the u parameter is interpreted as a null terminated string to use -// as the passphrase. If both cb and u are NULL then the default callback -// routine is used which will typically prompt for the passphrase on the -// current terminal with echoing turned off." -// -// Neat! -// -// Bonus points for being non-const as well. -constexpr inline void *empty_passphrase() { - return const_cast<void *>(static_cast<const void *>("")); -} - -// Attempt to read a PEM encoded (trusted) certificate from the given BIO. -// BIO might contain further certificates if function returns non-nullptr. -// Returns nullptr if no certificate could be loaded. This is usually an error, -// as this should be the first certificate in the chain. -X509Ptr read_trusted_x509_from_bio(::BIO& bio) { - // "_AUX" means the certificate is trusted. Why they couldn't name this function - // something with "trusted" instead is left as an exercise to the reader. - return X509Ptr(::PEM_read_bio_X509_AUX(&bio, nullptr, nullptr, empty_passphrase())); -} - -// Attempt to read a PEM encoded certificate from the given BIO. -// BIO might contain further certificates if function returns non-nullptr. -// Returns nullptr if no certificate could be loaded. This usually implies -// that there are no more certificates left in the chain. -X509Ptr read_untrusted_x509_from_bio(::BIO& bio) { - return X509Ptr(::PEM_read_bio_X509(&bio, nullptr, nullptr, empty_passphrase())); -} - -::SSL_CTX* new_tls1_2_ctx_with_auto_init() { - ensure_openssl_initialized_once(); - return ::SSL_CTX_new(::TLSv1_2_method()); -} - -} // anon ns - -OpenSslTlsContextImpl::OpenSslTlsContextImpl(const TransportSecurityOptions& ts_opts) - : _ctx(new_tls1_2_ctx_with_auto_init()) -{ - if (!_ctx) { - throw CryptoException("Failed to create new TLSv1.2 context"); - } - add_certificate_authorities(ts_opts.ca_certs_pem()); - add_certificate_chain(ts_opts.cert_chain_pem()); - use_private_key(ts_opts.private_key_pem()); - verify_private_key(); - enable_ephemeral_key_exchange(); - disable_compression(); - // TODO set accepted cipher suites! - // TODO `--> If not set in options, use Modern spec from https://wiki.mozilla.org/Security/Server_Side_TLS - // TODO set peer verification flags! -} - -OpenSslTlsContextImpl::~OpenSslTlsContextImpl() { - ::SSL_CTX_free(_ctx); -} - -void OpenSslTlsContextImpl::add_certificate_authorities(vespalib::stringref ca_pem) { - // TODO support empty CA set...? Ever useful? - auto bio = bio_from_string(ca_pem); - ::X509_STORE* cert_store = ::SSL_CTX_get_cert_store(_ctx); // Internal pointer, not owned by us. - while (true) { - auto ca_cert = read_untrusted_x509_from_bio(*bio); - if (!ca_cert) { - break; - } - if (::X509_STORE_add_cert(cert_store, ca_cert.get()) != 1) { // Does _not_ take ownership - throw CryptoException("X509_STORE_add_cert"); - } - } -} - -void OpenSslTlsContextImpl::add_certificate_chain(vespalib::stringref chain_pem) { - ::ERR_clear_error(); - auto bio = bio_from_string(chain_pem); - // First certificate in the chain is the node's own (trusted) certificate. - auto own_cert = read_trusted_x509_from_bio(*bio); - if (!own_cert) { - throw CryptoException("No X509 certificates could be found in provided chain"); - } - // Ownership of certificate is _not_ transferred, OpenSSL makes internal copy. - // This is not well documented, but is mentioned by other impls. - if (::SSL_CTX_use_certificate(_ctx, own_cert.get()) != 1) { - throw CryptoException("SSL_CTX_use_certificate"); - } - // After the node's own certificate comes any intermediate CA-provided certificates. - while (true) { - auto ca_cert = read_untrusted_x509_from_bio(*bio); - if (!ca_cert) { - // No more certificates in chain, hooray! - ::ERR_clear_error(); - break; - } - // Ownership of certificate _is_ transferred here! - if (!::SSL_CTX_add_extra_chain_cert(_ctx, ca_cert.release())) { - throw CryptoException("SSL_CTX_add_extra_chain_cert"); - } - } -} - -void OpenSslTlsContextImpl::use_private_key(vespalib::stringref key_pem) { - auto bio = bio_from_string(key_pem); - EvpPkeyPtr key(::PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, empty_passphrase())); - if (!key) { - throw CryptoException("Failed to read PEM private key data"); - } - // Ownership _not_ taken. - if (::SSL_CTX_use_PrivateKey(_ctx, key.get()) != 1) { - throw CryptoException("SSL_CTX_use_PrivateKey"); - } -} - -void OpenSslTlsContextImpl::verify_private_key() { - if (::SSL_CTX_check_private_key(_ctx) != 1) { - throw CryptoException("SSL_CTX_check_private_key failed; mismatch between public and private key?"); - } -} - -void OpenSslTlsContextImpl::enable_ephemeral_key_exchange() { - // Always enabled by default on higher versions. -#if (OPENSSL_VERSION_NUMBER < 0x10100000L) - // Auto curve selection is preferred over using SSL_CTX_set_ecdh_tmp - if (!::SSL_CTX_set_ecdh_auto(_ctx, 1)) { - throw CryptoException("SSL_CTX_set_ecdh_auto"); - } -#endif - // New ECDH key per connection. - ::SSL_CTX_set_options(_ctx, SSL_OP_SINGLE_ECDH_USE); -} - -void OpenSslTlsContextImpl::disable_compression() { - // TLS stream compression is vulnerable to a host of chosen plaintext - // attacks (CRIME, BREACH etc), so disable it. - ::SSL_CTX_set_options(_ctx, SSL_OP_NO_COMPRESSION); -} - -} 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 deleted file mode 100644 index 5fa982ee7ad..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#pragma once - -#include "openssl_typedefs.h" -#include <vespa/vespalib/net/tls/tls_context.h> -#include <vespa/vespalib/stllike/string.h> - -namespace vespalib::net::tls::impl { - -class OpenSslTlsContextImpl : public TlsContext { - ::SSL_CTX* _ctx; -public: - explicit OpenSslTlsContextImpl(const TransportSecurityOptions&); - ~OpenSslTlsContextImpl() override; - - ::SSL_CTX* native_context() const noexcept { return _ctx; } -private: - // Note: single use per instance; does _not_ clear existing chain! - void add_certificate_authorities(stringref ca_pem); - void add_certificate_chain(stringref chain_pem); - void use_private_key(stringref key_pem); - void verify_private_key(); - // Enable use of ephemeral key exchange (ECDHE), allowing forward secrecy. - void enable_ephemeral_key_exchange(); - void disable_compression(); -}; - -}
\ No newline at end of file diff --git a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_typedefs.h b/vespalib/src/vespa/vespalib/net/tls/impl/openssl_typedefs.h deleted file mode 100644 index 882ffde5897..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/impl/openssl_typedefs.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#pragma once - -#include <memory> -#include <openssl/ssl.h> -#include <openssl/crypto.h> -#include <openssl/x509.h> - -namespace vespalib::net::tls::impl { - -struct BioDeleter { - void operator()(::BIO* bio) const noexcept { - ::BIO_free(bio); - } -}; -using BioPtr = std::unique_ptr<::BIO, BioDeleter>; - -struct SslDeleter { - void operator()(::SSL* ssl) const noexcept { - ::SSL_free(ssl); - } -}; -using SslPtr = std::unique_ptr<::SSL, SslDeleter>; - -struct X509Deleter { - void operator()(::X509* cert) const noexcept { - ::X509_free(cert); - } -}; -using X509Ptr = std::unique_ptr<::X509, X509Deleter>; - -struct EvpPkeyDeleter { - void operator()(::EVP_PKEY* pkey) const noexcept { - ::EVP_PKEY_free(pkey); - } -}; -using EvpPkeyPtr = std::unique_ptr<::EVP_PKEY, EvpPkeyDeleter>; - -} diff --git a/vespalib/src/vespa/vespalib/net/tls/tls_context.cpp b/vespalib/src/vespa/vespalib/net/tls/tls_context.cpp deleted file mode 100644 index 467838975e7..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/tls_context.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#include "tls_context.h" -#include <vespa/vespalib/net/tls/impl/openssl_tls_context_impl.h> - -namespace vespalib::net::tls { - -std::unique_ptr<TlsContext> TlsContext::create_default_context(const TransportSecurityOptions& opts) { - return std::make_unique<impl::OpenSslTlsContextImpl>(opts); -} - -} diff --git a/vespalib/src/vespa/vespalib/net/tls/tls_context.h b/vespalib/src/vespa/vespalib/net/tls/tls_context.h deleted file mode 100644 index 7292f43f88c..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/tls_context.h +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -#pragma once - -#include <memory> - -namespace vespalib::net::tls { - -class TransportSecurityOptions; - -struct TlsContext { - virtual ~TlsContext() = default; - - static std::unique_ptr<TlsContext> create_default_context(const TransportSecurityOptions&); -}; - -} diff --git a/vespalib/src/vespa/vespalib/net/tls/transport_security_options.cpp b/vespalib/src/vespa/vespalib/net/tls/transport_security_options.cpp deleted file mode 100644 index 4e39fe4d7fa..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/transport_security_options.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#include "transport_security_options.h" -#include <openssl/crypto.h> - -namespace vespalib::net::tls { - -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 deleted file mode 100644 index 0a228388791..00000000000 --- a/vespalib/src/vespa/vespalib/net/tls/transport_security_options.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - -#pragma once - -#include <vespa/vespalib/stllike/string.h> - -namespace vespalib::net::tls { - -class TransportSecurityOptions { - vespalib::string _ca_certs_pem; - vespalib::string _cert_chain_pem; - vespalib::string _private_key_pem; -public: - TransportSecurityOptions() = default; - - TransportSecurityOptions(vespalib::string ca_certs_pem, - vespalib::string cert_chain_pem, - vespalib::string private_key_pem) - : _ca_certs_pem(std::move(ca_certs_pem)), - _cert_chain_pem(std::move(cert_chain_pem)), - _private_key_pem(std::move(private_key_pem)) - {} - ~TransportSecurityOptions(); - - const vespalib::string& ca_certs_pem() const noexcept { return _ca_certs_pem; } - const vespalib::string& cert_chain_pem() const noexcept { return _cert_chain_pem; } - const vespalib::string& private_key_pem() const noexcept { return _private_key_pem; } -}; - -} |