Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,21 @@ add_compile_options(-Wno-inconsistent-missing-override)
# https://github.com/madmongo1/blog-december-2020/blob/master/CMakeLists.txt
#
find_package(Boost 1.88 COMPONENTS thread atomic url filesystem process program_options OPTIONAL_COMPONENTS system REQUIRED)
find_package(OpenSSL)
find_package(Threads)

#
# TLS: BoringSSL -- or rather AWS-LC, which is API compatible and what the devcontainer builds into
# /opt/boringssl, deliberately away from the system OpenSSL. It only comes as static libraries.
#
# Everything in the process has to use the very same TLS library: Boost.Asio's SSL streams,
# ngtcp2_crypto_boringssl and our own code. Linking the system OpenSSL on top would silently mix
# up two libraries exporting the same symbols.
#
set(BORINGSSL_ROOT /opt/boringssl CACHE PATH "Installation prefix of BoringSSL / AWS-LC")
list(PREPEND CMAKE_PREFIX_PATH ${BORINGSSL_ROOT}) # ssl-config.cmake finds 'crypto' through it
find_package(ssl CONFIG REQUIRED)
message(STATUS "Using TLS library: ${ssl_DIR}")

#
# nghttp2
# https://github.com/curl/curl/blob/master/CMake/FindNGHTTP2.cmake
Expand Down
5 changes: 2 additions & 3 deletions include/anyhttp/h3_session.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include <nghttp3/nghttp3.h>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <ngtcp2/ngtcp2_crypto_ossl.h>

#include <openssl/ssl.h>

Expand Down Expand Up @@ -112,7 +111,7 @@ class Http3Session : public Session::Impl
int handle_expiry();

//
// ngtcp2 <-> ngtcp2_crypto_ossl bridge.
// ngtcp2 <-> ngtcp2_crypto_boringssl bridge, reached through SSL_get_app_data().
//
static ngtcp2_conn* get_conn(ngtcp2_crypto_conn_ref* ref)
{
Expand Down Expand Up @@ -219,7 +218,7 @@ class Http3Session : public Session::Impl
asio::any_io_executor executor_;

ngtcp2_conn* conn_ = nullptr;
ngtcp2_crypto_ossl_ctx* ossl_ctx_ = nullptr;
SSL* ssl_ = nullptr; // also ngtcp2's TLS native handle
ngtcp2_crypto_conn_ref conn_ref_{};

nghttp3_conn* h3_ = nullptr;
Expand Down
10 changes: 7 additions & 3 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ include_directories(${CMAKE_SOURCE_DIR}/include)
#
pkg_check_modules(NGTCP2 REQUIRED IMPORTED_TARGET libngtcp2)
pkg_check_modules(NGHTTP3 REQUIRED IMPORTED_TARGET libnghttp3)
pkg_check_modules(NGTCP2_CRYPTO_OSSL REQUIRED IMPORTED_TARGET libngtcp2_crypto_ossl)
pkg_check_modules(NGTCP2_CRYPTO_BORINGSSL REQUIRED IMPORTED_TARGET libngtcp2_crypto_boringssl)

#
# lib -- the protocol backends are the h1_*, h2_* and h3_* sources; only the h2_* ones use
Expand All @@ -23,11 +23,15 @@ target_include_directories(anyhttp PUBLIC ../include)

target_include_directories(anyhttp PRIVATE "/opt/nghttp3/build/include")
target_link_libraries(anyhttp PRIVATE Threads::Threads)
target_link_libraries(anyhttp PRIVATE OpenSSL::SSL)
target_link_libraries(anyhttp PRIVATE Boost::thread Boost::atomic Boost::url Boost::filesystem)
target_link_libraries(anyhttp PRIVATE spdlog::spdlog_header_only)
target_link_libraries(anyhttp PRIVATE PkgConfig::NGHTTP2)
target_link_libraries(anyhttp PRIVATE PkgConfig::NGTCP2 PkgConfig::NGHTTP3 PkgConfig::NGTCP2_CRYPTO_OSSL)
target_link_libraries(anyhttp PRIVATE PkgConfig::NGTCP2_CRYPTO_BORINGSSL PkgConfig::NGTCP2 PkgConfig::NGHTTP3)

# PUBLIC, because the backend headers include Boost.Asio's SSL streams: whoever includes them must
# see the BoringSSL headers, not the system OpenSSL ones in /usr/include. Static libraries, so this
# comes after ngtcp2_crypto_boringssl, which needs it.
target_link_libraries(anyhttp PUBLIC AWS::ssl)

#
# server
Expand Down
16 changes: 5 additions & 11 deletions src/h3_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
#include <nghttp3/nghttp3.h>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <ngtcp2/ngtcp2_crypto_ossl.h>
#include <ngtcp2/ngtcp2_crypto_boringssl.h>

#include <openssl/err.h>
#include <openssl/rand.h>
Expand Down Expand Up @@ -83,25 +83,19 @@ namespace
{

//
// One-shot process-wide initialization of ngtcp2_crypto_ossl and the client-role OpenSSL SSL_CTX
// used for every outgoing QUIC connection.
// The process-wide client-role BoringSSL SSL_CTX used for every outgoing QUIC connection.
//
struct TlsClientContext
{
TlsClientContext()
{
static const int init_once = []
{
if (ngtcp2_crypto_ossl_init() != 0)
throw std::runtime_error("ngtcp2_crypto_ossl_init");
return 0;
}();
(void)init_once;

ctx = SSL_CTX_new(TLS_client_method());
if (!ctx)
throw std::runtime_error("SSL_CTX_new");

if (ngtcp2_crypto_boringssl_configure_client_context(ctx) != 0)
throw std::runtime_error("ngtcp2_crypto_boringssl_configure_client_context");

static constexpr unsigned char alpn[] = "\x02h3";
SSL_CTX_set_alpn_protos(ctx, alpn, sizeof(alpn) - 1);

Expand Down
26 changes: 11 additions & 15 deletions src/h3_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
#include <nghttp3/nghttp3.h>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <ngtcp2/ngtcp2_crypto_ossl.h>
#include <ngtcp2/ngtcp2_crypto_boringssl.h>

#include <openssl/err.h>
#include <openssl/rand.h>
Expand Down Expand Up @@ -118,29 +118,25 @@ namespace
{

//
// One-shot process-wide initialization of ngtcp2_crypto_ossl and the OpenSSL SSL_CTX
// used for every QUIC connection.
// The process-wide BoringSSL SSL_CTX used for every QUIC connection.
//
struct TlsServerContext
{
TlsServerContext()
{
static const int init_once = []
{
if (ngtcp2_crypto_ossl_init() != 0)
throw std::runtime_error("ngtcp2_crypto_ossl_init");
return 0;
}();
(void)init_once;

ctx = SSL_CTX_new(TLS_server_method());
if (!ctx)
throw std::runtime_error("SSL_CTX_new");

SSL_CTX_set_options(ctx, (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
SSL_OP_SINGLE_ECDH_USE | SSL_OP_CIPHER_SERVER_PREFERENCE |
SSL_OP_NO_ANTI_REPLAY);
SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS);
if (ngtcp2_crypto_boringssl_configure_server_context(ctx) != 0)
throw std::runtime_error("ngtcp2_crypto_boringssl_configure_server_context");

//
// What OpenSSL needed SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS, SSL_OP_SINGLE_ECDH_USE and
// SSL_MODE_RELEASE_BUFFERS for is the default in BoringSSL. SSL_OP_NO_ANTI_REPLAY does not
// exist, but it would only matter for 0-RTT, which we don't enable.
//
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);

SSL_CTX_set_alpn_select_cb(ctx, &TlsServerContext::alpn_select_cb, nullptr);

Expand Down
44 changes: 14 additions & 30 deletions src/h3_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,10 @@ Http3Session::~Http3Session()
nghttp3_conn_del(h3_);
if (conn_)
ngtcp2_conn_del(conn_);
if (ossl_ctx_)
if (ssl_)
{
if (auto ssl = ngtcp2_crypto_ossl_ctx_get_ssl(ossl_ctx_))
{
SSL_set_app_data(ssl, nullptr);
SSL_free(ssl);
}
ngtcp2_crypto_ossl_ctx_del(ossl_ctx_);
SSL_set_app_data(ssl_, nullptr);
SSL_free(ssl_);
}
}

Expand Down Expand Up @@ -453,41 +449,29 @@ void Http3Session::fill_settings(ngtcp2_settings& settings, ngtcp2_transport_par
params.max_idle_timeout = static_cast<uint64_t>(idle_timeout.count());
}

//
// The QUIC specifics were configured on the SSL_CTX already (see the roles' TlsServerContext and
// TlsClientContext), so with BoringSSL, the session is a plain SSL whose app data leads back to us.
//
int Http3Session::setup_tls(SSL_CTX* ssl_ctx, bool is_server)
{
auto* ssl = SSL_new(ssl_ctx);
if (!ssl)
ssl_ = SSL_new(ssl_ctx);
if (!ssl_)
{
mloge("SSL_new failed");
return -1;
}

conn_ref_.get_conn = &Http3Session::get_conn;
conn_ref_.user_data = this;
SSL_set_app_data(ssl, &conn_ref_);
SSL_set_app_data(ssl_, &conn_ref_);

if (is_server)
SSL_set_accept_state(ssl);
SSL_set_accept_state(ssl_);
else
SSL_set_connect_state(ssl);

auto configure = is_server ? &ngtcp2_crypto_ossl_configure_server_session
: &ngtcp2_crypto_ossl_configure_client_session;
if (configure(ssl) != 0)
{
mloge("ngtcp2_crypto_ossl_configure_{}_session failed", is_server ? "server" : "client");
SSL_free(ssl);
return -1;
}

if (ngtcp2_crypto_ossl_ctx_new(&ossl_ctx_, ssl) != 0)
{
mloge("ngtcp2_crypto_ossl_ctx_new failed");
SSL_free(ssl);
return -1;
}
SSL_set_connect_state(ssl_);

ngtcp2_conn_set_tls_native_handle(conn_, ossl_ctx_);
ngtcp2_conn_set_tls_native_handle(conn_, ssl_);
return 0;
}

Expand Down Expand Up @@ -573,7 +557,7 @@ int Http3Session::cb_handshake_completed(ngtcp2_conn*, void* user)
{
auto self = static_cast<Http3Session*>(user);
logi("[{}] TLS handshake completed: {}", self->log_prefix_,
tls_handshake_info(ngtcp2_crypto_ossl_ctx_get_ssl(self->ossl_ctx_)));
tls_handshake_info(self->ssl_));
if (self->setup_http3() != 0)
return NGTCP2_ERR_CALLBACK_FAILURE;
return 0;
Expand Down
30 changes: 8 additions & 22 deletions src/tls.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#include <anyhttp/tls.hpp>

#include <openssl/evp.h>
#include <openssl/objects.h>
#include <openssl/ssl.h>

#include <format>
Expand All @@ -16,39 +15,26 @@ namespace

/**
* Key exchange group of the handshake, like the "Server Temp Key" line of h2load, e.g.
* "X25519 (253 bits)" or "prime256v1 (256 bits)".
* "X25519 (253 bits)" or "P-256 (256 bits)".
*/
std::string key_exchange(SSL* ssl)
{
//
// The negotiated group is known even for groups OpenSSL has no EVP_PKEY name for, like the
// post-quantum hybrids ("X25519MLKEM768") that are the default in OpenSSL 3.5.
// The negotiated group is known even for groups that have no EVP_PKEY, like the post-quantum
// hybrids ("X25519MLKEM768").
//
std::string name;
if (const char* group = SSL_get0_group_name(ssl))
name = group;
const char* group = SSL_get_group_name(SSL_get_group_id(ssl));
std::string name = group ? group : "unknown";

//
// On the client this is the server's key share, on the server the client's one. Either way,
// both peers agree on the group, which is what we are after.
//
EVP_PKEY* key = nullptr;
if (SSL_get_peer_tmp_key(ssl, &key) != 1 || !key)
return name.empty() ? "unknown" : name;

if (name.empty())
{
char group[80];
size_t len = 0;
if (EVP_PKEY_get_group_name(key, group, sizeof(group), &len) == 1 && len)
name.assign(group, len);
else if (const char* sn = OBJ_nid2sn(EVP_PKEY_get_id(key)))
name = sn;
else
name = "unknown";
}

auto bits = EVP_PKEY_get_bits(key);
return name;

auto bits = EVP_PKEY_bits(key);
EVP_PKEY_free(key);

return std::format("{} ({} bits)", name, bits);
Expand Down