// SPDX-License-Identifier: Apache-2.0 // Copyright 2024 Pionix GmbH and Contributors to EVerest #ifndef TLS_CONNECTION_TEST_HPP_ #define TLS_CONNECTION_TEST_HPP_ #include "extensions/helpers.hpp" #include #include #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; namespace { using tls::status_request::ClientStatusRequestV2; // ---------------------------------------------------------------------------- // set up code struct ClientStatusRequestV2Test : public ClientStatusRequestV2 { enum class flags_t : std::uint8_t { status_request_cb, status_request, status_request_v2, connected, last = connected, }; everest::lib::util::AtomicEnumFlags& flags; ClientStatusRequestV2Test() = delete; explicit ClientStatusRequestV2Test(everest::lib::util::AtomicEnumFlags& flag_ref) : flags(flag_ref) { } int status_request_cb(tls::Ssl* ctx) override { /* * This callback is called when status_request or status_request_v2 extensions * were present in the Client Hello. It doesn't mean that the extension is in * the Server Hello SSL_get_tlsext_status_ocsp_resp() returns -1 in that case */ int result{1}; const unsigned char* response{nullptr}; const auto total_length = SSL_get_tlsext_status_ocsp_resp(ctx, &response); flags.set(flags_t::status_request_cb); if ((response != nullptr) && (total_length > 0) && (total_length <= std::numeric_limits::max())) { switch (response[0]) { case 0x30: // a status_request response flags.set(flags_t::status_request); if (!print_ocsp_response(stdout, response, total_length)) { result = 0; } break; case 0x00: // a status_request_v2 response { flags.set(flags_t::status_request_v2); // multiple responses auto remaining = static_cast(total_length); const unsigned char* ptr{response}; while (remaining >= 3) { const auto len = tls::uint24(ptr); tls::update_position(ptr, remaining, 3); // print_ocsp_response updates tmp_p auto* tmp_p = ptr; const auto res = print_ocsp_response(stdout, tmp_p, len); tls::update_position(ptr, remaining, len); if (!res || (ptr != tmp_p)) { result = 0; remaining = -1; } } if (remaining != 0) { result = 0; } break; } default: break; } } return result; } }; struct ClientTest : public tls::Client { using flags_t = ClientStatusRequestV2Test::flags_t; everest::lib::util::AtomicEnumFlags flags; ClientTest() : tls::Client(std::unique_ptr(new ClientStatusRequestV2Test(flags))) { } void reset() { flags.reset(); } }; void handler(tls::Server::ConnectionPtr&& con) { if (con->accept() == tls::Connection::result_t::success) { std::uint32_t count{0}; std::array buffer{}; bool bExit = false; while (!bExit) { std::size_t readbytes = 0; std::size_t writebytes = 0; switch (con->read(buffer.data(), buffer.size(), readbytes)) { case tls::Connection::result_t::success: switch (con->write(buffer.data(), readbytes, writebytes)) { case tls::Connection::result_t::success: break; case tls::Connection::result_t::timeout: case tls::Connection::result_t::closed: default: bExit = true; break; } break; case tls::Connection::result_t::timeout: count++; if (count > 10) { bExit = true; } break; case tls::Connection::result_t::closed: default: bExit = true; break; } } con->shutdown(); } } void run_server(tls::Server& server) { server.serve(&handler); } class TlsTest : public testing::Test { protected: using flags_t = ClientTest::flags_t; tls::Server server; tls::Server::config_t server_config; std::thread server_thread; ClientTest client; tls::Client::config_t client_config; static void SetUpTestSuite() { struct sigaction action; std::memset(&action, 0, sizeof(action)); action.sa_handler = SIG_IGN; sigaction(SIGPIPE, &action, nullptr); tls::Server::configure_signal_handler(SIGUSR1); } void SetUp() override { server_config.cipher_list = "ECDHE-ECDSA-AES128-SHA256"; // server_config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384"; server_config.ciphersuites = ""; auto& ref0 = server_config.chains.emplace_back(); ref0.certificate_chain_file = "server_chain.pem"; ref0.private_key_file = "server_priv.pem"; ref0.trust_anchor_file = "server_root_cert.pem"; ref0.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"}; auto& ref1 = server_config.chains.emplace_back(); ref1.certificate_chain_file = "alt_server_chain.pem"; ref1.private_key_file = "alt_server_priv.pem"; ref1.trust_anchor_file = "alt_server_root_cert.pem"; ref1.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"}; // server_config.verify_locations_file = "client_root_cert.pem"; server_config.host = "127.0.0.1"; server_config.service = "8444"; server_config.ipv6_only = false; server_config.verify_client = false; server_config.io_timeout_ms = 1000; // no lower than 200ms, valgrind need much higher client_config.cipher_list = "ECDHE-ECDSA-AES128-SHA256"; // client_config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384"; // client_config.certificate_chain_file = "client_chain.pem"; // client_config.private_key_file = "client_priv.pem"; client_config.verify_locations_file = "server_root_cert.pem"; client_config.io_timeout_ms = 1000; client_config.verify_server = true; client_config.status_request = false; client_config.status_request_v2 = false; client.reset(); } void TearDown() override { server.stop(); server.wait_stopped(); if (server_thread.joinable()) { server_thread.join(); } } void start(const tls::Server::ConfigurationCallback& init_ssl = nullptr) { using state_t = tls::Server::state_t; const auto res = server.init(server_config, init_ssl); if ((res == state_t::init_complete) || (res == state_t::init_socket)) { server_thread = std::thread(&run_server, std::ref(server)); server.wait_running(); } } void start(const std::function& handler) { using state_t = tls::Server::state_t; const auto res = server.init(server_config, nullptr); if ((res == state_t::init_complete) || (res == state_t::init_socket)) { server_thread = std::thread([this, handler]() { this->server.serve(handler); }); server.wait_running(); } } void connect(const std::function& handler = nullptr) { client.init(client_config); client.reset(); // localhost works in some cases but not in the CI pipeline for IPv6 // use ip6-localhost auto connection = client.connect("127.0.0.1", "8444", false, 1000); if (handler == nullptr) { if (connection) { if (connection->connect() == tls::Connection::result_t::success) { set(ClientTest::flags_t::connected); connection->shutdown(); } } } else { handler(connection); } } void set(flags_t flag) { client.flags.set(flag); } [[nodiscard]] bool is_set(flags_t flag) const { return client.flags.is_set(flag); } [[nodiscard]] bool is_reset(flags_t flag) const { return client.flags.is_reset(flag); } void add_ta_cert_hash(const char* filename) { openssl::sha_1_digest_t digest; auto certs = openssl::load_certificates(filename); for (const auto& ta : certs) { if (openssl::certificate_sha_1(digest, ta.get())) { client_config.trusted_ca_keys_data.cert_sha1_hash.push_back(digest); } } } void add_ta_key_hash(const char* filename) { openssl::sha_1_digest_t digest; auto certs = openssl::load_certificates(filename); for (const auto& ta : certs) { if (openssl::certificate_subject_public_key_sha_1(digest, ta.get())) { client_config.trusted_ca_keys_data.key_sha1_hash.push_back(digest); } } } void add_ta_name(const char* filename) { auto certs = openssl::load_certificates(filename); for (const auto& ta : certs) { auto res = openssl::certificate_subject_der(ta.get()); if (res) { client_config.trusted_ca_keys_data.x509_name.emplace_back(std::move(res)); } } } }; class TlsTestTpm : public TlsTest { protected: void SetUp() override { server_config.cipher_list = "ECDHE-ECDSA-AES128-SHA256"; // server_config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384"; server_config.ciphersuites = ""; auto& ref0 = server_config.chains.emplace_back(); ref0.certificate_chain_file = "tpm_pki/server_chain.pem"; ref0.private_key_file = "tpm_pki/server_priv.pem"; ref0.trust_anchor_file = "tpm_pki/server_root_cert.pem"; ref0.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"}; // auto& ref1 = server_config.chains.emplace_back(); // ref1.certificate_chain_file = "alt_server_chain.pem"; // ref1.private_key_file = "alt_server_priv.pem"; // ref1.trust_anchor_file = "alt_server_root_cert.pem"; // ref1.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"}; server_config.host = "127.0.0.1"; server_config.service = "8444"; server_config.ipv6_only = false; server_config.verify_client = false; server_config.io_timeout_ms = 1000; // no lower than 200ms, valgrind need much higher client_config.cipher_list = "ECDHE-ECDSA-AES128-SHA256"; // client_config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384"; // client_config.certificate_chain_file = "client_chain.pem"; // client_config.private_key_file = "client_priv.pem"; client_config.verify_locations_file = "tpm_pki/server_root_cert.pem"; client_config.io_timeout_ms = 1000; client_config.verify_server = true; client_config.status_request = false; client_config.status_request_v2 = false; client.reset(); } }; } // namespace #endif // TLS_CONNECTION_TEST_HPP_