diff --git a/conanfile.py b/conanfile.py index 049147397b24..a1fdfc54ecf0 100644 --- a/conanfile.py +++ b/conanfile.py @@ -106,8 +106,9 @@ def set_version(self): ) major_version = re.search(r'set\(USERVER_MAJOR_VERSION (.*)\)', content).group(1).strip() minor_version = re.search(r'set\(USERVER_MINOR_VERSION (.*)\)', content).group(1).strip() + hotfix_version = 1 - self.version = f'{major_version}.{minor_version}' # pylint: disable=attribute-defined-outside-init + self.version = f'{major_version}.{minor_version}.{hotfix_version}.1' # pylint: disable=attribute-defined-outside-init def layout(self): cmake_layout(self) diff --git a/core/include/userver/engine/io/tls_wrapper.hpp b/core/include/userver/engine/io/tls_wrapper.hpp index aaecf95080c1..3efc0e147912 100644 --- a/core/include/userver/engine/io/tls_wrapper.hpp +++ b/core/include/userver/engine/io/tls_wrapper.hpp @@ -6,6 +6,8 @@ #include #include +#include +//#include #include #include #include @@ -42,10 +44,8 @@ class [[nodiscard]] TlsWrapper final : public RwBase { /// Starts a TLS server on an opened socket static TlsWrapper StartTlsServer( Socket&& socket, - const crypto::CertificatesChain& cert_chain, - const crypto::PrivateKey& key, - Deadline deadline, - const std::vector& extra_cert_authorities = {} + const crypto::SslCtx& ctx, + Deadline deadline ); ~TlsWrapper() override; diff --git a/core/src/clients/http/client_crl_test.cpp b/core/src/clients/http/client_crl_test.cpp index 2efaecbdc819..926830c6260b 100644 --- a/core/src/clients/http/client_crl_test.cpp +++ b/core/src/clients/http/client_crl_test.cpp @@ -315,12 +315,14 @@ struct TlsServer { auto deadline = engine::Deadline::FromDuration(utest::kMaxTestWaitTime); auto socket = tcp_listener_.socket.Accept(deadline); - auto tls_server = engine::io::TlsWrapper::StartTlsServer( - std::move(socket), + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( crypto::LoadCertificatesChainFromString(kServerCertificate), crypto::PrivateKey::LoadFromString(kRevokedServerPrivateKey), - deadline, - cas + cas); + auto tls_server = engine::io::TlsWrapper::StartTlsServer( + std::move(socket), + ssl_ctx, + deadline ); std::array data{}; diff --git a/core/src/engine/io/tls_wrapper.cpp b/core/src/engine/io/tls_wrapper.cpp index ccd4890aef31..9c453e783bfc 100644 --- a/core/src/engine/io/tls_wrapper.cpp +++ b/core/src/engine/io/tls_wrapper.cpp @@ -21,11 +21,6 @@ USERVER_NAMESPACE_BEGIN namespace engine::io { namespace { -struct SslCtxDeleter { - void operator()(SSL_CTX* ctx) const noexcept { SSL_CTX_free(ctx); } -}; -using SslCtx = std::unique_ptr; - struct SslDeleter { void operator()(SSL* ssl) const noexcept { SSL_free(ssl); } }; @@ -190,65 +185,11 @@ int SSL_write_ex(SSL* ssl, const void* data, size_t len, size_t* bytes_written) } #endif -SslCtx MakeSslCtx() { - crypto::Openssl::Init(); - - SslCtx ssl_ctx{SSL_CTX_new(SSLv23_method())}; - if (!ssl_ctx) { - throw TlsException(crypto::FormatSslError("Failed create an SSL context: SSL_CTX_new")); - } -#if OPENSSL_VERSION_NUMBER >= 0x010100000L - if (1 != SSL_CTX_set_min_proto_version(ssl_ctx.get(), TLS1_VERSION)) { - throw TlsException(crypto::FormatSslError("Failed create an SSL context: SSL_CTX_set_min_proto_version")); - } -#endif - - constexpr auto options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION -#if OPENSSL_VERSION_NUMBER >= 0x010100000L - | SSL_OP_NO_RENEGOTIATION -#endif - ; - SSL_CTX_set_options(ssl_ctx.get(), options); - SSL_CTX_set_mode(ssl_ctx.get(), SSL_MODE_ENABLE_PARTIAL_WRITE); - SSL_CTX_clear_mode(ssl_ctx.get(), SSL_MODE_AUTO_RETRY); - if (1 != SSL_CTX_set_default_verify_paths(ssl_ctx.get())) { - LOG_LIMITED_WARNING() << crypto::FormatSslError("Failed create an SSL context: SSL_CTX_set_default_verify_paths" - ); - } - return ssl_ctx; -} - enum InterruptAction { kPass, kFail, }; -void SetServerName(SslCtx& ctx, std::string_view server_name) { - if (server_name.empty()) { - return; - } - - X509_VERIFY_PARAM* verify_param = SSL_CTX_get0_param(ctx.get()); - if (!verify_param) { - throw TlsException("Failed to set up client TLS wrapper: SSL_CTX_get0_param"); - } - if (1 != X509_VERIFY_PARAM_set1_host(verify_param, server_name.data(), server_name.size())) { - throw TlsException(crypto::FormatSslError("Failed to set up client TLS wrapper: X509_VERIFY_PARAM_set1_host")); - } - SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER, nullptr); -} - -void AddCertAuthorities(SslCtx& ctx, const std::vector& cert_authorities) { - UASSERT(!cert_authorities.empty()); - auto* store = SSL_CTX_get_cert_store(ctx.get()); - UASSERT(store); - for (const auto& ca : cert_authorities) { - if (1 != X509_STORE_add_cert(store, ca.GetNative())) { - throw TlsException(crypto::FormatSslError("Failed to set up client TLS wrapper: X509_STORE_add_cert")); - } - } -} - } // namespace class TlsWrapper::ReadContextAccessor final : public engine::impl::ContextAccessor { @@ -284,7 +225,7 @@ class TlsWrapper::Impl { SyncBioData(SSL_get_rbio(ssl.get()), &other.bio_data); } - void SetUp(SslCtx&& ssl_ctx) { + void SetUp(const crypto::SslCtx& ssl_ctx) { Bio socket_bio{BIO_new(GetSocketBioMethod())}; if (!socket_bio) { throw TlsException(crypto::FormatSslError("Failed to set up TLS wrapper: BIO_new")); @@ -293,7 +234,7 @@ class TlsWrapper::Impl { SyncBioData(socket_bio.get(), nullptr); BIO_set_init(socket_bio.get(), 1); - ssl.reset(SSL_new(ssl_ctx.get())); + ssl.reset(SSL_new(static_cast(ssl_ctx.GetRawSslCtx()))); if (!ssl) { throw TlsException(crypto::FormatSslError("Failed to set up TLS wrapper: SSL_new")); } @@ -454,11 +395,8 @@ engine::impl::ContextAccessor& TlsWrapper::ReadContextAccessor::GetSocketContext TlsWrapper::TlsWrapper(Socket&& socket) : impl_(std::move(socket)) { SetupContextAccessors(); } TlsWrapper TlsWrapper::StartTlsClient(Socket&& socket, const std::string& server_name, Deadline deadline) { - auto ssl_ctx = MakeSslCtx(); - SetServerName(ssl_ctx, server_name); - TlsWrapper wrapper{std::move(socket)}; - wrapper.impl_->SetUp(std::move(ssl_ctx)); + wrapper.impl_->SetUp(crypto::SslCtx::CreateClientTlsContext(server_name)); wrapper.impl_->ClientConnect(server_name, deadline); return wrapper; } @@ -471,79 +409,19 @@ TlsWrapper TlsWrapper::StartTlsClient( Deadline deadline, const std::vector& extra_cert_authorities ) { - auto ssl_ctx = MakeSslCtx(); - SetServerName(ssl_ctx, server_name); - - if (!extra_cert_authorities.empty()) { - AddCertAuthorities(ssl_ctx, extra_cert_authorities); - } - - if (cert) { - if (1 != SSL_CTX_use_certificate(ssl_ctx.get(), cert.GetNative())) { - throw TlsException(crypto::FormatSslError("Failed to set up client TLS wrapper: SSL_CTX_use_certificate")); - } - } - - if (key) { - if (1 != SSL_CTX_use_PrivateKey(ssl_ctx.get(), key.GetNative())) { - throw TlsException(crypto::FormatSslError("Failed to set up client TLS wrapper: SSL_CTX_use_PrivateKey")); - } - } - TlsWrapper wrapper{std::move(socket)}; - wrapper.impl_->SetUp(std::move(ssl_ctx)); + wrapper.impl_->SetUp(crypto::SslCtx::CreateClientTlsContext(server_name, cert, key, extra_cert_authorities)); wrapper.impl_->ClientConnect(server_name, deadline); return wrapper; } TlsWrapper TlsWrapper::StartTlsServer( Socket&& socket, - const crypto::CertificatesChain& cert_chain, - const crypto::PrivateKey& key, - Deadline deadline, - const std::vector& extra_cert_authorities + const crypto::SslCtx& ctx, + Deadline deadline ) { - auto ssl_ctx = MakeSslCtx(); - - if (!extra_cert_authorities.empty()) { - AddCertAuthorities(ssl_ctx, extra_cert_authorities); - SSL_CTX_set_verify(ssl_ctx.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); - LOG_INFO() << "Client SSL cert will be verified"; - } else { - LOG_INFO() << "Client SSL cert will not be verified"; - } - - if (cert_chain.empty()) { - throw TlsException(crypto::FormatSslError("Empty certificate chain provided")); - } - - if (1 != SSL_CTX_use_certificate(ssl_ctx.get(), cert_chain.begin()->GetNative())) { - throw TlsException(crypto::FormatSslError("Failed to set up server TLS wrapper: SSL_CTX_use_certificate")); - } - - if (cert_chain.size() > 1) { - auto cert_it = std::next(cert_chain.begin()); - for (; cert_it != cert_chain.end(); ++cert_it) { - // cast in openssl1.0 macro expansion - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) - if (SSL_CTX_add_extra_chain_cert(ssl_ctx.get(), cert_it->GetNative()) <= 0) { - throw TlsException( - crypto::FormatSslError("Failed to set up server TLS wrapper: SSL_CTX_add_extra_chain_cert") - ); - } - - // After SSL_CTX_add_extra_chain_cert we should not free the cert - const auto ret = X509_up_ref(cert_it->GetNative()); - UASSERT(ret == 1); - } - } - - if (1 != SSL_CTX_use_PrivateKey(ssl_ctx.get(), key.GetNative())) { - throw TlsException(crypto::FormatSslError("Failed to set up server TLS wrapper: SSL_CTX_use_PrivateKey")); - } - TlsWrapper wrapper{std::move(socket)}; - wrapper.impl_->SetUp(std::move(ssl_ctx)); + wrapper.impl_->SetUp(ctx); wrapper.impl_->bio_data.current_deadline = deadline; auto ret = SSL_accept(wrapper.impl_->ssl.get()); diff --git a/core/src/engine/io/tls_wrapper_benchmark.cpp b/core/src/engine/io/tls_wrapper_benchmark.cpp index e617bf19cc34..33379b6a0184 100644 --- a/core/src/engine/io/tls_wrapper_benchmark.cpp +++ b/core/src/engine/io/tls_wrapper_benchmark.cpp @@ -93,12 +93,13 @@ constexpr auto kDeadlineMaxTime = std::chrono::seconds{60}; auto [server, client] = tcp_listener.MakeSocketPair(deadline); std::atomic reading{true}; + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext(crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); auto server_task = engine::AsyncNoSpan( - [&reading, deadline](auto&& server) { + [&reading, deadline, &ssl_ctx](auto&& server) { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, deadline ); @@ -137,12 +138,13 @@ BENCHMARK(tls_write_all_buffered)->RangeMultiplier(2)->Range(1 << 6, 1 << 12)->U auto [server, client] = tcp_listener.MakeSocketPair(deadline); std::atomic reading{true}; + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext(crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); auto server_task = engine::AsyncNoSpan( - [&reading, deadline](auto&& server) { + [&reading, deadline, &ssl_ctx](auto&& server) { auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, deadline ); diff --git a/core/src/engine/io/tls_wrapper_test.cpp b/core/src/engine/io/tls_wrapper_test.cpp index 68cfa30eb1a9..ca1591062297 100644 --- a/core/src/engine/io/tls_wrapper_test.cpp +++ b/core/src/engine/io/tls_wrapper_test.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -232,10 +233,11 @@ UTEST(TlsWrapper, InitListSmall) { auto server_task = utils::Async( "tls-server", [deadline, kDataA, kDataB, kDataC, kDataD](auto&& server) { + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext(crypto::LoadCertificatesChainFromString(cert_chain), + crypto::PrivateKey::LoadFromString(chain_private_key)); auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert_chain), - crypto::PrivateKey::LoadFromString(chain_private_key), + ssl_ctx, deadline ); if (tls_server.WriteAll({kDataA, kDataB, kDataC, kDataD}, deadline) != @@ -272,10 +274,12 @@ UTEST(TlsWrapper, InitListLarge) { auto server_task = utils::Async( "tls-server", [deadline, kDataA, kDataB, kDataC, kDataD](auto&& server) { + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, deadline ); if (tls_server.WriteAll({kDataA, kDataB, kDataC, kDataD}, deadline) != @@ -308,10 +312,12 @@ UTEST(TlsWrapper, InitListSmallThenLarge) { auto server_task = utils::Async( "tls-server", [deadline, kDataSmall, kDataLarge](auto&& server) { + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, deadline ); if (tls_server.WriteAll({kDataSmall, kDataSmall, kDataSmall, kDataSmall, kDataLarge}, deadline) != @@ -340,10 +346,12 @@ UTEST_MT(TlsWrapper, Smoke, 2) { auto server_task = engine::AsyncNoSpan( [test_deadline](auto&& server) { try { + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, test_deadline ); EXPECT_EQ(1, tls_server.SendAll("1", 1, test_deadline)); @@ -387,10 +395,12 @@ UTEST_MT(TlsWrapper, DocTest, 2) { auto server_task = utils::Async( "tls-server", [deadline](auto&& server) { + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, deadline ); if (tls_server.SendAll(kData.data(), kData.size(), deadline) != kData.size()) { @@ -419,10 +429,12 @@ UTEST(TlsWrapper, Move) { auto server_task = engine::AsyncNoSpan( [test_deadline](auto&& server) { try { + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, test_deadline ); engine::AsyncNoSpan( @@ -468,11 +480,13 @@ UTEST(TlsWrapper, ConnectTimeout) { static_cast(io::TlsWrapper::StartTlsClient(std::move(client), {}, Deadline::FromDuration(kShortTimeout))), io::IoTimeout ); + + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext(crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); EXPECT_THROW( static_cast(io::TlsWrapper::StartTlsServer( std::move(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, Deadline::FromDuration(kShortTimeout) )), io::IoException @@ -488,10 +502,12 @@ UTEST_MT(TlsWrapper, IoTimeout, 2) { engine::SingleConsumerEvent timeout_happened; auto server_task = engine::AsyncNoSpan( [test_deadline, &timeout_happened](auto&& server) { + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, test_deadline ); char c = 0; @@ -525,10 +541,12 @@ UTEST(TlsWrapper, Cancel) { auto server_task = engine::AsyncNoSpan( [test_deadline](auto&& server) { + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, test_deadline ); char c = 0; @@ -552,20 +570,23 @@ UTEST_MT(TlsWrapper, CertKeyMismatch, 2) { auto server_task = engine::AsyncNoSpan( [test_deadline](auto&& server) { UEXPECT_THROW( + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(other_key)); static_cast(io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(other_key), + ssl_ctx, test_deadline )), - io::TlsException + crypto::CryptoException ); }, std::move(server) ); EXPECT_THROW( - static_cast(io::TlsWrapper::StartTlsClient(std::move(client), {}, test_deadline)), io::IoException + static_cast(io::TlsWrapper::StartTlsClient(std::move(client), {}, test_deadline)), + engine::io::IoSystemError ); server_task.Get(); } @@ -579,13 +600,15 @@ UTEST_MT(TlsWrapper, NonTlsClient, 2) { auto server_task = engine::AsyncNoSpan( [test_deadline](auto&& server) { UEXPECT_THROW( + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(other_key)); static_cast(io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(other_key), + ssl_ctx, test_deadline )), - io::TlsException + crypto::CryptoException ); }, std::move(server) @@ -620,10 +643,12 @@ UTEST_MT(TlsWrapper, DoubleSmoke, 4) { auto server_task = engine::AsyncNoSpan( [test_deadline](auto&& server) { + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, test_deadline ); EXPECT_EQ(1, tls_server.SendAll("1", 1, test_deadline)); @@ -641,10 +666,12 @@ UTEST_MT(TlsWrapper, DoubleSmoke, 4) { auto other_server_task = engine::AsyncNoSpan( [test_deadline](auto&& server) { + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(other_cert), + crypto::PrivateKey::LoadFromString(other_key)); auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(other_cert), - crypto::PrivateKey::LoadFromString(other_key), + ssl_ctx, test_deadline ); EXPECT_EQ(1, tls_server.SendAll("5", 1, test_deadline)); @@ -694,10 +721,10 @@ UTEST(TlsWrapper, InvalidSocket) { const auto test_deadline = Deadline::FromDuration(utest::kMaxTestWaitTime); UEXPECT_THROW(static_cast(io::TlsWrapper::StartTlsClient({}, {}, test_deadline)), io::TlsException); + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext(crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); UEXPECT_THROW( - static_cast(io::TlsWrapper::StartTlsServer( - {}, crypto::LoadCertificatesChainFromString(cert), crypto::PrivateKey::LoadFromString(key), test_deadline - )), + static_cast(io::TlsWrapper::StartTlsServer({}, ssl_ctx, test_deadline)), io::TlsException ); } @@ -711,10 +738,12 @@ UTEST(TlsWrapper, PeerShutdown) { auto server_task = engine::AsyncNoSpan( [test_deadline](auto&& server) { try { + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, test_deadline ); char c = 0; @@ -753,10 +782,12 @@ UTEST(TlsWrapper, PeerDisconnect) { auto server_task = engine::AsyncNoSpan( [test_deadline](auto&& server) { try { + crypto::SslCtx ssl_ctx = crypto::SslCtx::CreateServerTlsContext( + crypto::LoadCertificatesChainFromString(cert), + crypto::PrivateKey::LoadFromString(key)); auto tls_server = io::TlsWrapper::StartTlsServer( std::forward(server), - crypto::LoadCertificatesChainFromString(cert), - crypto::PrivateKey::LoadFromString(key), + ssl_ctx, test_deadline ); char c = 0; diff --git a/core/src/server/net/listener_config.cpp b/core/src/server/net/listener_config.cpp index 8ba15e978d17..bac0b5dd5e1e 100644 --- a/core/src/server/net/listener_config.cpp +++ b/core/src/server/net/listener_config.cpp @@ -9,8 +9,9 @@ #include -USERVER_NAMESPACE_BEGIN +#include "userver/engine/io.hpp" +USERVER_NAMESPACE_BEGIN namespace server::net { namespace { @@ -102,6 +103,13 @@ void PortConfig::ReadTlsSettings(const storages::secdist::SecdistConfig& secdist } } +void PortConfig::InitSslCtx() { + if (tls) { + ssl_ctx = crypto::SslCtx::CreateServerTlsContext(tls_cert_chain, tls_private_key, tls_certificate_authorities); + } +} + + } // namespace server::net USERVER_NAMESPACE_END diff --git a/core/src/server/net/listener_config.hpp b/core/src/server/net/listener_config.hpp index 76c36e69ff32..8456fa16a959 100644 --- a/core/src/server/net/listener_config.hpp +++ b/core/src/server/net/listener_config.hpp @@ -6,10 +6,13 @@ #include #include #include +#include #include #include #include +#include + #include "connection_config.hpp" USERVER_NAMESPACE_BEGIN @@ -29,7 +32,10 @@ struct PortConfig { crypto::PrivateKey tls_private_key; std::vector tls_certificate_authorities; + std::optional ssl_ctx; + void ReadTlsSettings(const storages::secdist::SecdistConfig& secdist); + void InitSslCtx(); }; struct ListenerConfig { diff --git a/core/src/server/net/listener_impl.cpp b/core/src/server/net/listener_impl.cpp index f52f08d97863..2f27e888908f 100644 --- a/core/src/server/net/listener_impl.cpp +++ b/core/src/server/net/listener_impl.cpp @@ -104,12 +104,10 @@ void ListenerImpl::ProcessConnection(engine::io::Socket peer_socket, const PortC std::unique_ptr socket; auto remote_address = peer_socket.Getpeername(); if (port_config.tls) { + UASSERT(port_config.ssl_ctx.has_value()); socket = std::make_unique(engine::io::TlsWrapper::StartTlsServer( std::move(peer_socket), - port_config.tls_cert_chain, - port_config.tls_private_key, - {}, - port_config.tls_certificate_authorities + port_config.ssl_ctx.value(), {} )); } else { socket = std::make_unique(std::move(peer_socket)); diff --git a/core/src/server/server.cpp b/core/src/server/server.cpp index 20700f382f1f..c0ceb466775a 100644 --- a/core/src/server/server.cpp +++ b/core/src/server/server.cpp @@ -154,7 +154,10 @@ ServerImpl::ServerImpl( : config_(std::move(config)) { LOG_DEBUG() << "Creating server"; - for (auto& port : config_.listener.ports) port.ReadTlsSettings(secdist); + for (auto& port : config_.listener.ports) { + port.ReadTlsSettings(secdist); + port.InitSslCtx(); + } main_port_info_.Init(config_, config_.listener, component_context, false); if (config_.max_response_size_in_flight) { diff --git a/universal/include/userver/crypto/certificate.hpp b/universal/include/userver/crypto/certificate.hpp index d50e6c64818c..42f59393007f 100644 --- a/universal/include/userver/crypto/certificate.hpp +++ b/universal/include/userver/crypto/certificate.hpp @@ -42,6 +42,9 @@ class Certificate { /// @throw crypto::KeyParseError if failed to load the certificate. static Certificate LoadFromStringSkippingAttributes(std::string_view certificate); + /// Returns Subject + std::string GetSubject() const; + private: explicit Certificate(std::shared_ptr cert) : cert_(std::move(cert)) {} diff --git a/universal/include/userver/crypto/ssl_ctx.hpp b/universal/include/userver/crypto/ssl_ctx.hpp new file mode 100644 index 000000000000..901b37f64678 --- /dev/null +++ b/universal/include/userver/crypto/ssl_ctx.hpp @@ -0,0 +1,61 @@ +#pragma once + +/// @file userver/crypto/ssl_ctx.hpp +/// @brief @copybrief crypto::SslCtx + +#include +#include + +#include +#include + +USERVER_NAMESPACE_BEGIN + +namespace crypto { + +/// @ingroup userver_universal userver_containers +/// +/// SSL context +class SslCtx { +public: + static SslCtx CreateServerTlsContext( + const crypto::CertificatesChain& cert_chain, + const crypto::PrivateKey& key, + const std::vector& extra_cert_authorities = {} + ); + + static SslCtx CreateClientTlsContext(const std::string& server_name); + + static SslCtx CreateClientTlsContext( + const std::string& server_name, + const crypto::Certificate& cert, + const crypto::PrivateKey& key, + const std::vector& extra_cert_authorities = {} + ); + + SslCtx(SslCtx&&) noexcept; + SslCtx& operator=(SslCtx&&) noexcept; + ~SslCtx(); + + SslCtx(const SslCtx&) = delete; + SslCtx& operator=(const SslCtx&) = delete; + + void* GetRawSslCtx() const noexcept; + +private: + void AddCertAuthorities(const std::vector& cert_authorities); + void EnableVerifyClientCertificate(); + void SetServerName(std::string_view server_name); + void SetCertificate(const crypto::Certificate& cert); + void SetCertificates(const crypto::CertificatesChain& cert_chain); + void SetPrivateKey(const crypto::PrivateKey& key); + + class Impl; + std::unique_ptr impl_{}; + + explicit SslCtx(std::unique_ptr&& impl); +}; + +} // namespace crypto + +USERVER_NAMESPACE_END \ No newline at end of file diff --git a/universal/src/crypto/certificate.cpp b/universal/src/crypto/certificate.cpp index 9f8ac5abf942..2acc5bf271fc 100644 --- a/universal/src/crypto/certificate.cpp +++ b/universal/src/crypto/certificate.cpp @@ -86,6 +86,38 @@ CertificatesChain LoadCertificatesChainFromString(std::string_view chain) { return certificates; } +std::string Certificate::GetSubject() const { + X509* x509 = GetNative(); + if (!x509) { + throw KeyParseError(FormatSslError("Invalid certificate")); + } + + X509_NAME* subject_name = X509_get_subject_name(x509); + if (!subject_name) { + throw KeyParseError(FormatSslError("Failed to get subject name from certificate")); + } + + // Используем BIO для более гибкого форматирования + BIO* bio = BIO_new(BIO_s_mem()); + if (!bio) { + throw KeyParseError(FormatSslError("Failed to create BIO")); + } + + // Флаги форматирования можно настроить по вкусу + const int flags = XN_FLAG_RFC2253; // компактная строка, как в DN + if (X509_NAME_print_ex(bio, subject_name, 0, flags) < 0) { + BIO_free(bio); + throw KeyParseError(FormatSslError("Failed to print subject name")); + } + + char* data = nullptr; + long len = BIO_get_mem_data(bio, &data); + std::string result(data, len); + + BIO_free(bio); + return result; +} + } // namespace crypto USERVER_NAMESPACE_END diff --git a/universal/src/crypto/ssl_ctx.cpp b/universal/src/crypto/ssl_ctx.cpp new file mode 100644 index 000000000000..8ea09a8c45d2 --- /dev/null +++ b/universal/src/crypto/ssl_ctx.cpp @@ -0,0 +1,200 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + +USERVER_NAMESPACE_BEGIN +namespace crypto { + +class SslCtx::Impl { +public: + static std::unique_ptr MakeSslCtx(); + + explicit Impl(SSL_CTX* ctx) : ctx_{ctx} { + } + + ~Impl() { + if (ctx_) SSL_CTX_free(ctx_); + } + + SSL_CTX* Get() noexcept { return ctx_; } + +private: + SSL_CTX* ctx_; +}; + +std::unique_ptr SslCtx::Impl::MakeSslCtx() { + + crypto::Openssl::Init(); + + SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_method()); + if (!ssl_ctx) { + throw CryptoException(crypto::FormatSslError("Failed create an SSL context: SSL_CTX_new")); + } +#if OPENSSL_VERSION_NUMBER >= 0x010100000L + if (1 != SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_VERSION)) { + throw CryptoException(crypto::FormatSslError("Failed create an SSL context: SSL_CTX_set_min_proto_version")); + } +#endif + + constexpr auto options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION +#if OPENSSL_VERSION_NUMBER >= 0x010100000L + | SSL_OP_NO_RENEGOTIATION +#endif + ; + SSL_CTX_set_options(ssl_ctx, options); + SSL_CTX_set_mode(ssl_ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_clear_mode(ssl_ctx, SSL_MODE_AUTO_RETRY); + if (1 != SSL_CTX_set_default_verify_paths(ssl_ctx)) { + LOG_LIMITED_WARNING() << crypto::FormatSslError("Failed create an SSL context: SSL_CTX_set_default_verify_paths" + ); + } + + return std::make_unique(ssl_ctx); +} + +void* SslCtx::GetRawSslCtx() const noexcept { + return static_cast(impl_->Get()); +} + +SslCtx::SslCtx(std::unique_ptr&& impl) : impl_(std::move(impl)) {} + +SslCtx::SslCtx(SslCtx&& other) noexcept = default; +SslCtx& SslCtx::operator=(SslCtx&& other) noexcept = default; +SslCtx::~SslCtx() = default; + + +void SslCtx::AddCertAuthorities(const std::vector& cert_authorities) { + UASSERT(!cert_authorities.empty()); + auto* store = SSL_CTX_get_cert_store(impl_->Get()); + UASSERT(store); + for (const auto& ca : cert_authorities) { + if (1 != X509_STORE_add_cert(store, ca.GetNative())) { + throw CryptoException(crypto::FormatSslError("X509_STORE_add_cert failed")); + } + } +} + +void SslCtx::EnableVerifyClientCertificate() { + SSL_CTX_set_verify(impl_->Get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); +} + +void SslCtx::SetServerName(std::string_view server_name) { + if (server_name.empty()) { + return; + } + + X509_VERIFY_PARAM* verify_param = SSL_CTX_get0_param(impl_->Get()); + if (!verify_param) { + throw CryptoException("SSL_CTX_get0_param failed"); + } + if (1 != X509_VERIFY_PARAM_set1_host(verify_param, server_name.data(), server_name.size())) { + throw CryptoException(crypto::FormatSslError("X509_VERIFY_PARAM_set1_host failed")); + } + SSL_CTX_set_verify(impl_->Get(), SSL_VERIFY_PEER, nullptr); +} + +void SslCtx::SetCertificate(const crypto::Certificate& cert) { + if (1 != SSL_CTX_use_certificate(impl_->Get(), cert.GetNative())) + { + throw CryptoException(crypto::FormatSslError("SSL_CTX_use_certificate failed")); + } + + LOG_INFO() << "Loaded server cert: subject=" << cert.GetSubject(); +} + +void SslCtx::SetCertificates(const crypto::CertificatesChain& cert_chain) { + + if (cert_chain.empty()) { + throw CryptoException(crypto::FormatSslError("Empty certificate chain provided")); + } + + SetCertificate(*cert_chain.begin()); + + if (cert_chain.size() > 1) { + auto it = std::next(cert_chain.begin()); + for (; it != cert_chain.end(); ++it) { + // cast in openssl1.0 macro expansion + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + if (SSL_CTX_add_extra_chain_cert(impl_->Get(), it->GetNative()) <= 0) { + throw CryptoException(crypto::FormatSslError("SSL_CTX_add_extra_chain_cert failed")); + } + + LOG_INFO() << "Loaded extra chain cert: subject=" << it->GetSubject(); + + // After SSL_CTX_add_extra_chain_cert we should not free the cert + const auto ret = X509_up_ref(it->GetNative()); + UASSERT(ret == 1); + } + } +} + +void SslCtx::SetPrivateKey(const crypto::PrivateKey& key) { + if (1 != SSL_CTX_use_PrivateKey(impl_->Get(), key.GetNative())) { + throw CryptoException(crypto::FormatSslError("SSL_CTX_use_PrivateKey failed")); + } + + LOG_INFO() << "Loaded server private key"; + + if (1 != SSL_CTX_check_private_key(impl_->Get())) { + throw CryptoException("Private key does not match the certificate public key"); + } +} + +SslCtx SslCtx::CreateClientTlsContext(const std::string& server_name) { + SslCtx ssl_ctx(Impl::MakeSslCtx()); + ssl_ctx.SetServerName(server_name); + return ssl_ctx; +} + +SslCtx SslCtx::CreateClientTlsContext( + const std::string& server_name, + const crypto::Certificate& cert, + const crypto::PrivateKey& key, + const std::vector& extra_cert_authorities +) { + + SslCtx ssl_ctx(Impl::MakeSslCtx()); + + ssl_ctx.SetServerName(server_name); + + if (!extra_cert_authorities.empty()) { + ssl_ctx.AddCertAuthorities(extra_cert_authorities); + } + + ssl_ctx.SetCertificate(cert); + ssl_ctx.SetPrivateKey(key); + + return ssl_ctx; +} + +SslCtx SslCtx::CreateServerTlsContext( + const crypto::CertificatesChain& cert_chain, + const crypto::PrivateKey& key, + const std::vector& extra_cert_authorities) +{ + SslCtx ssl_ctx(Impl::MakeSslCtx()); + + if (!extra_cert_authorities.empty()) { + ssl_ctx.AddCertAuthorities(extra_cert_authorities); + ssl_ctx.EnableVerifyClientCertificate(); + LOG_INFO() << "Client SSL cert will be verified"; + } else { + LOG_INFO() << "Client SSL cert will not be verified"; + } + + ssl_ctx.SetCertificates(cert_chain); + ssl_ctx.SetPrivateKey(key); + + return ssl_ctx; +} + +} // namespace crypto + +USERVER_NAMESPACE_END \ No newline at end of file