deps: add ngtcp2 test binaries

The goal here is to add the ngtcp2 client and server
samples so that we can use them in CI to test our QUIC
implementation.

PR-URL: https://github.com/nodejs/node/pull/59946
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
This commit is contained in:
James M Snell 2025-09-20 07:13:25 -07:00
parent 2e5c8dff9c
commit 65a32bac18
215 changed files with 133809 additions and 33 deletions

View File

@ -67,6 +67,17 @@
'sources': [
'<@(nghttp2_sources)',
]
},
{
'target_name': 'sfparse',
'type': 'static_library',
'include_dirs': ['lib'],
'sources': [
'lib/sfparse.c',
],
'direct_dependent_settings': {
'include_dirs': [ 'lib/includes' ]
}
}
]
}

177
deps/ngtcp2/ngtcp2.gyp vendored
View File

@ -87,6 +87,36 @@
'nghttp3/lib/nghttp3_unreachable.c',
'nghttp3/lib/nghttp3_vec.c',
'nghttp3/lib/nghttp3_version.c',
],
'ngtcp2_test_server_sources': [
'ngtcp2/examples/tls_server_session_ossl.cc',
'ngtcp2/examples/tls_server_context_ossl.cc',
'ngtcp2/examples/tls_session_base_ossl.cc',
'ngtcp2/examples/util_openssl.cc',
'ngtcp2/examples/http.cc',
'ngtcp2/examples/server_base.cc',
'ngtcp2/examples/shared.cc',
'ngtcp2/examples/server.cc',
'ngtcp2/examples/util.cc',
'ngtcp2/examples/debug.cc',
'ngtcp2/examples/siphash.cc',
'ngtcp2/third-party/urlparse/urlparse.c',
'ngtcp2/third-party/libev/ev.c',
],
'ngtcp2_test_client_sources': [
'ngtcp2/examples/tls_client_session_ossl.cc',
'ngtcp2/examples/tls_client_context_ossl.cc',
'ngtcp2/examples/tls_session_base_ossl.cc',
'ngtcp2/examples/util_openssl.cc',
'ngtcp2/examples/http.cc',
'ngtcp2/examples/client_base.cc',
'ngtcp2/examples/shared.cc',
'ngtcp2/examples/client.cc',
'ngtcp2/examples/util.cc',
'ngtcp2/examples/debug.cc',
'ngtcp2/examples/siphash.cc',
'ngtcp2/third-party/urlparse/urlparse.c',
'ngtcp2/third-party/libev/ev.c',
]
},
'targets': [
@ -131,19 +161,6 @@
'HAVE_NETINET_IN_H',
],
}],
# TODO: Support OpenSSL 3.5 shared library builds.
# The complexity here is that we need to use the ngtcp2 ossl
# adapter, which does not include any conditional checks to
# see if the version of OpenSSL used has the necessary QUIC
# APIs, so we need to ensure that we conditionally enable use
# of the adapter only when we know that the OpenSSL version we
# are compiling against has the necessary APIs. We can do that
# by checkig the OpenSSL version number but, currently, the
# code that does so checks only the VERSION.dat file that is
# bundled with the openssl dependency. We'll need to update
# that to support the shared library case, where the version
# of the shared library needs to be determined.
#
# TODO: Support Boringssl here also. ngtcp2 provides an adapter
# for Boringssl. If we can detect that boringssl is being used
# here then we can use that adapter and also set the
@ -225,6 +242,140 @@
'sources': [
'<@(nghttp3_sources)'
]
},
{
'target_name': 'ngtcp2_test_server',
'type': 'executable',
'cflags': [ '-Wno-everything' ],
'include_dirs': [
'',
'ngtcp2/examples/',
'ngtcp2/lib/includes/',
'ngtcp2/crypto/includes/',
'ngtcp2/third-party/urlparse/',
'ngtcp2/third-party/libev/',
'../../nghttp2/lib',
],
'dependencies': [
'ngtcp2',
'nghttp3',
'../openssl/openssl.gyp:openssl',
'../nghttp2/nghttp2.gyp:sfparse',
],
'defines': [
'HAVE_CONFIG_H',
'WITH_EXAMPLE_OSSL',
'EV_STANDALONE=1',
],
'conditions': [
['OS=="mac"', {
'defines': [
'__APPLE_USE_RFC_3542',
]
}],
['OS=="solaris"', {
'defines': [
'IPTOS_ECN_MASK=0x03',
],
'link_settings': {
'libraries': [ '-lsocket', '-lnsl' ],
},
}],
['OS=="win"', {
'defines': [
'WIN32',
'_WINDOWS',
],
'msvs_settings': {
'VCCLCompilerTool': {
'CompileAs': '1'
},
},
}],
['OS!="win"', {
'defines': [
'HAVE_UNISTD_H',
'HAVE_ARPA_INET_H',
'HAVE_NETINET_IN_H',
'HAVE_NETINET_IP_H',
],
}],
[ 'OS=="linux" or OS=="openharmony"', {
'link_settings': {
'libraries': [ '-ldl', '-lrt' ],
},
}],
],
'sources': [
'<@(ngtcp2_test_server_sources)'
]
},
{
'target_name': 'ngtcp2_test_client',
'type': 'executable',
'cflags': [ '-Wno-everything' ],
'include_dirs': [
'',
'ngtcp2/examples/',
'ngtcp2/lib/includes/',
'ngtcp2/crypto/includes/',
'ngtcp2/third-party/urlparse/',
'ngtcp2/third-party/libev/',
'../../nghttp2/lib',
],
'dependencies': [
'ngtcp2',
'nghttp3',
'../openssl/openssl.gyp:openssl',
'../nghttp2/nghttp2.gyp:sfparse',
],
'defines': [
'HAVE_CONFIG_H',
'WITH_EXAMPLE_OSSL',
'EV_STANDALONE=1',
],
'conditions': [
['OS=="mac"', {
'defines': [
'__APPLE_USE_RFC_3542',
]
}],
['OS=="solaris"', {
'defines': [
'IPTOS_ECN_MASK=0x03',
],
'link_settings': {
'libraries': [ '-lsocket', '-lnsl' ],
},
}],
['OS=="win"', {
'defines': [
'WIN32',
'_WINDOWS',
],
'msvs_settings': {
'VCCLCompilerTool': {
'CompileAs': '1'
},
},
}],
['OS!="win"', {
'defines': [
'HAVE_UNISTD_H',
'HAVE_ARPA_INET_H',
'HAVE_NETINET_IN_H',
'HAVE_NETINET_IP_H',
],
}],
[ 'OS=="linux" or OS=="openharmony"', {
'link_settings': {
'libraries': [ '-ldl', '-lrt' ],
},
}],
],
'sources': [
'<@(ngtcp2_test_client_sources)'
]
}
]
}

3379
deps/ngtcp2/ngtcp2/examples/client.cc vendored Normal file

File diff suppressed because it is too large Load Diff

198
deps/ngtcp2/ngtcp2/examples/client.h vendored Normal file
View File

@ -0,0 +1,198 @@
/*
* ngtcp2
*
* Copyright (c) 2017 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef CLIENT_H
#define CLIENT_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <vector>
#include <deque>
#include <unordered_map>
#include <string_view>
#include <memory>
#include <span>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <nghttp3/nghttp3.h>
#include <ev.h>
#include "client_base.h"
#include "tls_client_context.h"
#include "tls_client_session.h"
#include "network.h"
#include "shared.h"
#include "template.h"
using namespace ngtcp2;
struct Stream {
Stream(const Request &req, int64_t stream_id);
~Stream();
int open_file(const std::string_view &path);
Request req;
int64_t stream_id;
int fd;
};
class Client;
struct Endpoint {
Address addr;
ev_io rev;
Client *client;
int fd;
};
class Client : public ClientBase {
public:
Client(struct ev_loop *loop, uint32_t client_chosen_version,
uint32_t original_version);
~Client();
int init(int fd, const Address &local_addr, const Address &remote_addr,
const char *addr, const char *port, TLSClientContext &tls_ctx);
void disconnect();
int on_read(const Endpoint &ep);
int on_write();
int write_streams();
int feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen,
const ngtcp2_pkt_info *pi, std::span<const uint8_t> data);
int handle_expiry();
void update_timer();
int handshake_completed();
int handshake_confirmed();
void recv_version_negotiation(const uint32_t *sv, size_t nsv);
int send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr,
unsigned int ecn, std::span<const uint8_t> data);
std::pair<std::span<const uint8_t>, int>
send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr,
unsigned int ecn, std::span<const uint8_t> data, size_t gso_size);
int send_packet_or_blocked(const ngtcp2_path &path, unsigned int ecn,
std::span<const uint8_t> data, size_t gso_size);
int on_stream_close(int64_t stream_id, uint64_t app_error_code);
int on_extend_max_streams();
int handle_error();
int make_stream_early();
int change_local_addr();
void start_change_local_addr_timer();
int update_key(uint8_t *rx_secret, uint8_t *tx_secret,
ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
const uint8_t *current_rx_secret,
const uint8_t *current_tx_secret, size_t secretlen);
int initiate_key_update();
void start_key_update_timer();
void start_delay_stream_timer();
int select_preferred_address(Address &selected_addr,
const ngtcp2_preferred_addr *paddr);
std::optional<Endpoint *> endpoint_for(const Address &remote_addr);
void set_remote_addr(const ngtcp2_addr &remote_addr);
int setup_httpconn();
int submit_http_request(const Stream *stream);
int recv_stream_data(uint32_t flags, int64_t stream_id,
std::span<const uint8_t> data);
int acked_stream_data_offset(int64_t stream_id, uint64_t datalen);
void http_consume(int64_t stream_id, size_t nconsumed);
void http_write_data(int64_t stream_id, std::span<const uint8_t> data);
int on_stream_reset(int64_t stream_id);
int on_stream_stop_sending(int64_t stream_id);
int extend_max_stream_data(int64_t stream_id, uint64_t max_data);
int stop_sending(int64_t stream_id, uint64_t app_error_code);
int reset_stream(int64_t stream_id, uint64_t app_error_code);
int http_stream_close(int64_t stream_id, uint64_t app_error_code);
void on_send_blocked(const ngtcp2_path &path, unsigned int ecn,
std::span<const uint8_t> data, size_t gso_size);
void start_wev_endpoint(const Endpoint &ep);
int send_blocked_packet();
ngtcp2_ssize write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest,
size_t destlen, ngtcp2_tstamp ts);
const std::vector<uint32_t> &get_offered_versions() const;
bool get_early_data() const;
void early_data_rejected();
bool should_exit() const;
private:
std::vector<Endpoint> endpoints_;
Address remote_addr_;
ev_io wev_;
ev_timer timer_;
ev_timer change_local_addr_timer_;
ev_timer key_update_timer_;
ev_timer delay_stream_timer_;
ev_signal sigintev_;
struct ev_loop *loop_;
std::unordered_map<int64_t, std::unique_ptr<Stream>> streams_;
std::vector<uint32_t> offered_versions_;
nghttp3_conn *httpconn_;
// addr_ is the server host address.
const char *addr_;
// port_ is the server port.
const char *port_;
// nstreams_done_ is the number of streams opened.
size_t nstreams_done_;
// nstreams_closed_ is the number of streams get closed.
size_t nstreams_closed_;
// nkey_update_ is the number of key update occurred.
size_t nkey_update_;
uint32_t client_chosen_version_;
uint32_t original_version_;
// early_data_ is true if client attempts to do 0RTT data transfer.
bool early_data_;
// handshake_confirmed_ gets true after handshake has been
// confirmed.
bool handshake_confirmed_;
bool no_gso_;
struct {
bool send_blocked;
// blocked field is effective only when send_blocked is true.
struct {
const Endpoint *endpoint;
Address remote_addr;
unsigned int ecn;
std::span<const uint8_t> data;
size_t gso_size;
} blocked;
std::array<uint8_t, 64_k> data;
} tx_;
};
#endif // !defined(CLIENT_H)

View File

@ -0,0 +1,207 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "client_base.h"
#include <cassert>
#include <array>
#include <iostream>
#include <fstream>
#include "debug.h"
#include "template.h"
#include "util.h"
using namespace ngtcp2;
using namespace std::literals;
extern Config config;
static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
auto c = static_cast<ClientBase *>(conn_ref->user_data);
return c->conn();
}
ClientBase::ClientBase()
: conn_ref_{get_conn, this},
qlog_(nullptr),
conn_(nullptr),
ticket_received_(false) {
ngtcp2_ccerr_default(&last_error_);
}
ClientBase::~ClientBase() {
if (conn_) {
ngtcp2_conn_del(conn_);
}
if (qlog_) {
fclose(qlog_);
}
}
int ClientBase::write_transport_params(const char *path,
const ngtcp2_transport_params *params) {
auto f = std::ofstream(path);
if (!f) {
return -1;
}
f << "initial_max_streams_bidi=" << params->initial_max_streams_bidi << '\n'
<< "initial_max_streams_uni=" << params->initial_max_streams_uni << '\n'
<< "initial_max_stream_data_bidi_local="
<< params->initial_max_stream_data_bidi_local << '\n'
<< "initial_max_stream_data_bidi_remote="
<< params->initial_max_stream_data_bidi_remote << '\n'
<< "initial_max_stream_data_uni=" << params->initial_max_stream_data_uni
<< '\n'
<< "initial_max_data=" << params->initial_max_data << '\n'
<< "active_connection_id_limit=" << params->active_connection_id_limit
<< '\n'
<< "max_datagram_frame_size=" << params->max_datagram_frame_size << '\n';
f.close();
if (!f) {
return -1;
}
return 0;
}
int ClientBase::read_transport_params(const char *path,
ngtcp2_transport_params *params) {
auto f = std::ifstream(path);
if (!f) {
return -1;
}
for (std::string line; std::getline(f, line);) {
if (util::istarts_with(line, "initial_max_streams_bidi="sv)) {
if (auto n = util::parse_uint(line.c_str() +
"initial_max_streams_bidi="sv.size());
!n) {
return -1;
} else {
params->initial_max_streams_bidi = *n;
}
continue;
}
if (util::istarts_with(line, "initial_max_streams_uni="sv)) {
if (auto n = util::parse_uint(line.c_str() +
"initial_max_streams_uni="sv.size());
!n) {
return -1;
} else {
params->initial_max_streams_uni = *n;
}
continue;
}
if (util::istarts_with(line, "initial_max_stream_data_bidi_local="sv)) {
if (auto n = util::parse_uint(
line.c_str() + "initial_max_stream_data_bidi_local="sv.size());
!n) {
return -1;
} else {
params->initial_max_stream_data_bidi_local = *n;
}
continue;
}
if (util::istarts_with(line, "initial_max_stream_data_bidi_remote="sv)) {
if (auto n = util::parse_uint(
line.c_str() + "initial_max_stream_data_bidi_remote="sv.size());
!n) {
return -1;
} else {
params->initial_max_stream_data_bidi_remote = *n;
}
continue;
}
if (util::istarts_with(line, "initial_max_stream_data_uni="sv)) {
if (auto n = util::parse_uint(line.c_str() +
"initial_max_stream_data_uni="sv.size());
!n) {
return -1;
} else {
params->initial_max_stream_data_uni = *n;
}
continue;
}
if (util::istarts_with(line, "initial_max_data="sv)) {
if (auto n =
util::parse_uint(line.c_str() + "initial_max_data="sv.size());
!n) {
return -1;
} else {
params->initial_max_data = *n;
}
continue;
}
if (util::istarts_with(line, "active_connection_id_limit="sv)) {
if (auto n = util::parse_uint(line.c_str() +
"active_connection_id_limit="sv.size());
!n) {
return -1;
} else {
params->active_connection_id_limit = *n;
}
continue;
}
if (util::istarts_with(line, "max_datagram_frame_size="sv)) {
if (auto n = util::parse_uint(line.c_str() +
"max_datagram_frame_size="sv.size());
!n) {
return -1;
} else {
params->max_datagram_frame_size = *n;
}
continue;
}
}
return 0;
}
ngtcp2_conn *ClientBase::conn() const { return conn_; }
void qlog_write_cb(void *user_data, uint32_t flags, const void *data,
size_t datalen) {
auto c = static_cast<ClientBase *>(user_data);
c->write_qlog(data, datalen);
}
void ClientBase::write_qlog(const void *data, size_t datalen) {
assert(qlog_);
fwrite(data, 1, datalen, qlog_);
}
ngtcp2_crypto_conn_ref *ClientBase::conn_ref() { return &conn_ref_; }
void ClientBase::ticket_received() { ticket_received_ = true; }

View File

@ -0,0 +1,230 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef CLIENT_BASE_H
#define CLIENT_BASE_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <vector>
#include <deque>
#include <string>
#include <string_view>
#include <functional>
#include <ngtcp2/ngtcp2_crypto.h>
#include "tls_client_session.h"
#include "network.h"
#include "shared.h"
using namespace ngtcp2;
struct Request {
std::string_view scheme;
std::string authority;
std::string path;
};
struct Config {
ngtcp2_cid dcid;
ngtcp2_cid scid;
bool scid_present;
// tx_loss_prob is probability of losing outgoing packet.
double tx_loss_prob;
// rx_loss_prob is probability of losing incoming packet.
double rx_loss_prob;
// fd is a file descriptor to read input for streams.
int fd;
// ciphers is the list of enabled ciphers.
const char *ciphers;
// groups is the list of supported groups.
const char *groups;
// nstreams is the number of streams to open.
size_t nstreams;
// data is the pointer to memory region which maps file denoted by
// fd.
uint8_t *data;
// datalen is the length of file denoted by fd.
size_t datalen;
// version is a QUIC version to use.
uint32_t version;
// quiet suppresses the output normally shown except for the error
// messages.
bool quiet;
// timeout is an idle timeout for QUIC connection.
ngtcp2_duration timeout;
// session_file is a path to a file to write, and read TLS session.
const char *session_file;
// tp_file is a path to a file to write, and read QUIC transport
// parameters.
const char *tp_file;
// show_secret is true if transport secrets should be printed out.
bool show_secret;
// change_local_addr is the duration after which client changes
// local address.
ngtcp2_duration change_local_addr;
// key_update is the duration after which client initiates key
// update.
ngtcp2_duration key_update;
// delay_stream is the duration after which client sends the first
// 1-RTT stream.
ngtcp2_duration delay_stream;
// nat_rebinding is true if simulated NAT rebinding is enabled.
bool nat_rebinding;
// no_preferred_addr is true if client do not follow preferred
// address offered by server.
bool no_preferred_addr;
std::string_view http_method;
// download is a path to a directory where a downloaded file is
// saved. If it is empty, no file is saved.
std::string_view download;
// requests contains URIs to request.
std::vector<Request> requests;
// no_quic_dump is true if hexdump of QUIC STREAM and CRYPTO data
// should be disabled.
bool no_quic_dump;
// no_http_dump is true if hexdump of HTTP response body should be
// disabled.
bool no_http_dump;
// qlog_file is the path to write qlog.
std::string_view qlog_file;
// qlog_dir is the path to directory where qlog is stored. qlog_dir
// and qlog_file are mutually exclusive.
std::string_view qlog_dir;
// max_data is the initial connection-level flow control window.
uint64_t max_data;
// max_stream_data_bidi_local is the initial stream-level flow
// control window for a bidirectional stream that the local endpoint
// initiates.
uint64_t max_stream_data_bidi_local;
// max_stream_data_bidi_remote is the initial stream-level flow
// control window for a bidirectional stream that the remote
// endpoint initiates.
uint64_t max_stream_data_bidi_remote;
// max_stream_data_uni is the initial stream-level flow control
// window for a unidirectional stream.
uint64_t max_stream_data_uni;
// max_streams_bidi is the number of the concurrent bidirectional
// streams.
uint64_t max_streams_bidi;
// max_streams_uni is the number of the concurrent unidirectional
// streams.
uint64_t max_streams_uni;
// max_window is the maximum connection-level flow control window
// size if auto-tuning is enabled.
uint64_t max_window;
// max_stream_window is the maximum stream-level flow control window
// size if auto-tuning is enabled.
uint64_t max_stream_window;
// exit_on_first_stream_close is the flag that if it is true, client
// exits when a first HTTP stream gets closed. It is not
// necessarily the same time when the underlying QUIC stream closes
// due to the QPACK synchronization.
bool exit_on_first_stream_close;
// exit_on_all_streams_close is the flag that if it is true, client
// exits when all HTTP streams get closed.
bool exit_on_all_streams_close;
// disable_early_data disables early data.
bool disable_early_data;
// static_secret is used to derive keying materials for Stateless
// Retry token.
std::array<uint8_t, 32> static_secret;
// cc_algo is the congestion controller algorithm.
ngtcp2_cc_algo cc_algo;
// token_file is a path to file to read or write token from
// NEW_TOKEN frame.
std::string_view token_file;
// sni is the value sent in TLS SNI, overriding DNS name of the
// remote host.
std::string_view sni;
// initial_rtt is an initial RTT.
ngtcp2_duration initial_rtt;
// max_udp_payload_size is the maximum UDP payload size that client
// transmits.
size_t max_udp_payload_size;
// handshake_timeout is the period of time before giving up QUIC
// connection establishment.
ngtcp2_duration handshake_timeout;
// preferred_versions includes QUIC versions in the order of
// preference. Client uses this field to select a version from the
// version set offered in Version Negotiation packet.
std::vector<uint32_t> preferred_versions;
// available_versions includes QUIC versions that are sent in
// available_versions field of version_information
// transport_parameter.
std::vector<uint32_t> available_versions;
// no_pmtud disables Path MTU Discovery.
bool no_pmtud;
// ack_thresh is the minimum number of the received ACK eliciting
// packets that triggers immediate acknowledgement.
size_t ack_thresh;
// wait_for_ticket, if true, waits for a ticket to be received
// before exiting on exit_on_first_stream_close or
// exit_on_all_streams_close.
bool wait_for_ticket;
// initial_pkt_num is the initial packet number for each packet
// number space. If it is set to UINT32_MAX, it is chosen randomly.
uint32_t initial_pkt_num;
// pmtud_probes is the array of UDP datagram payload size to probes.
std::vector<uint16_t> pmtud_probes;
// ech_config_list contains ECHConfigList.
std::vector<uint8_t> ech_config_list;
// ech_config_list_file is a path to a file to read and write
// ECHConfigList.
const char *ech_config_list_file;
};
class ClientBase {
public:
ClientBase();
~ClientBase();
ngtcp2_conn *conn() const;
int write_transport_params(const char *path,
const ngtcp2_transport_params *params);
int read_transport_params(const char *path, ngtcp2_transport_params *params);
void write_qlog(const void *data, size_t datalen);
ngtcp2_crypto_conn_ref *conn_ref();
void ticket_received();
protected:
ngtcp2_crypto_conn_ref conn_ref_;
TLSClientSession tls_session_;
FILE *qlog_;
ngtcp2_conn *conn_;
ngtcp2_ccerr last_error_;
bool ticket_received_;
};
void qlog_write_cb(void *user_data, uint32_t flags, const void *data,
size_t datalen);
#endif // !defined(CLIENT_BASE_H)

322
deps/ngtcp2/ngtcp2/examples/debug.cc vendored Normal file
View File

@ -0,0 +1,322 @@
/*
* ngtcp2
*
* Copyright (c) 2017 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "debug.h"
#include <unistd.h>
#include <cassert>
#include <random>
#include <iostream>
#include <array>
#include "util.h"
using namespace std::literals;
namespace ngtcp2 {
namespace debug {
namespace {
auto randgen = util::make_mt19937();
} // namespace
namespace {
auto *outfile = stderr;
} // namespace
int handshake_completed(ngtcp2_conn *conn, void *user_data) {
fprintf(outfile, "QUIC handshake has completed\n");
return 0;
}
int handshake_confirmed(ngtcp2_conn *conn, void *user_data) {
fprintf(outfile, "QUIC handshake has been confirmed\n");
return 0;
}
bool packet_lost(double prob) {
auto p = std::uniform_real_distribution<>(0, 1)(randgen);
return p < prob;
}
void print_crypto_data(ngtcp2_encryption_level encryption_level,
std::span<const uint8_t> data) {
const char *encryption_level_str;
switch (encryption_level) {
case NGTCP2_ENCRYPTION_LEVEL_INITIAL:
encryption_level_str = "Initial";
break;
case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
encryption_level_str = "Handshake";
break;
case NGTCP2_ENCRYPTION_LEVEL_1RTT:
encryption_level_str = "1-RTT";
break;
default:
assert(0);
abort();
}
fprintf(outfile, "Ordered CRYPTO data in %s crypto level\n",
encryption_level_str);
util::hexdump(outfile, data.data(), data.size());
}
void print_stream_data(int64_t stream_id, std::span<const uint8_t> data) {
fprintf(outfile, "Ordered STREAM data stream_id=0x%" PRIx64 "\n", stream_id);
util::hexdump(outfile, data.data(), data.size());
}
void print_initial_secret(std::span<const uint8_t> data) {
fprintf(outfile, "initial_secret=%s\n", util::format_hex(data).c_str());
}
void print_client_in_secret(std::span<const uint8_t> data) {
fprintf(outfile, "client_in_secret=%s\n", util::format_hex(data).c_str());
}
void print_server_in_secret(std::span<const uint8_t> data) {
fprintf(outfile, "server_in_secret=%s\n", util::format_hex(data).c_str());
}
void print_handshake_secret(std::span<const uint8_t> data) {
fprintf(outfile, "handshake_secret=%s\n", util::format_hex(data).c_str());
}
void print_client_hs_secret(std::span<const uint8_t> data) {
fprintf(outfile, "client_hs_secret=%s\n", util::format_hex(data).c_str());
}
void print_server_hs_secret(std::span<const uint8_t> data) {
fprintf(outfile, "server_hs_secret=%s\n", util::format_hex(data).c_str());
}
void print_client_0rtt_secret(std::span<const uint8_t> data) {
fprintf(outfile, "client_0rtt_secret=%s\n", util::format_hex(data).c_str());
}
void print_client_1rtt_secret(std::span<const uint8_t> data) {
fprintf(outfile, "client_1rtt_secret=%s\n", util::format_hex(data).c_str());
}
void print_server_1rtt_secret(std::span<const uint8_t> data) {
fprintf(outfile, "server_1rtt_secret=%s\n", util::format_hex(data).c_str());
}
void print_client_pp_key(std::span<const uint8_t> data) {
fprintf(outfile, "+ client_pp_key=%s\n", util::format_hex(data).c_str());
}
void print_server_pp_key(std::span<const uint8_t> data) {
fprintf(outfile, "+ server_pp_key=%s\n", util::format_hex(data).c_str());
}
void print_client_pp_iv(std::span<const uint8_t> data) {
fprintf(outfile, "+ client_pp_iv=%s\n", util::format_hex(data).c_str());
}
void print_server_pp_iv(std::span<const uint8_t> data) {
fprintf(outfile, "+ server_pp_iv=%s\n", util::format_hex(data).c_str());
}
void print_client_pp_hp(std::span<const uint8_t> data) {
fprintf(outfile, "+ client_pp_hp=%s\n", util::format_hex(data).c_str());
}
void print_server_pp_hp(std::span<const uint8_t> data) {
fprintf(outfile, "+ server_pp_hp=%s\n", util::format_hex(data).c_str());
}
void print_secrets(std::span<const uint8_t> secret,
std::span<const uint8_t> key, std::span<const uint8_t> iv,
std::span<const uint8_t> hp) {
std::cerr << "+ secret=" << util::format_hex(secret) << "\n"
<< "+ key=" << util::format_hex(key) << "\n"
<< "+ iv=" << util::format_hex(iv) << "\n"
<< "+ hp=" << util::format_hex(hp) << std::endl;
}
void print_secrets(std::span<const uint8_t> secret,
std::span<const uint8_t> key, std::span<const uint8_t> iv) {
std::cerr << "+ secret=" << util::format_hex(secret) << "\n"
<< "+ key=" << util::format_hex(key) << "\n"
<< "+ iv=" << util::format_hex(iv) << std::endl;
}
void print_hp_mask(std::span<const uint8_t> mask,
std::span<const uint8_t> sample) {
fprintf(outfile, "mask=%s sample=%s\n", util::format_hex(mask).c_str(),
util::format_hex(sample).c_str());
}
void log_printf(void *user_data, const char *fmt, ...) {
va_list ap;
std::array<char, 4096> buf;
va_start(ap, fmt);
auto n = vsnprintf(buf.data(), buf.size(), fmt, ap);
va_end(ap);
if (static_cast<size_t>(n) >= buf.size()) {
n = buf.size() - 1;
}
buf[static_cast<size_t>(n++)] = '\n';
while (write(fileno(stderr), buf.data(), static_cast<size_t>(n)) == -1 &&
errno == EINTR)
;
}
void path_validation(const ngtcp2_path *path,
ngtcp2_path_validation_result res) {
auto local_addr = util::straddr(
reinterpret_cast<sockaddr *>(path->local.addr), path->local.addrlen);
auto remote_addr = util::straddr(
reinterpret_cast<sockaddr *>(path->remote.addr), path->remote.addrlen);
std::cerr << "Path validation against path {local:" << local_addr
<< ", remote:" << remote_addr << "} "
<< (res == NGTCP2_PATH_VALIDATION_RESULT_SUCCESS ? "succeeded"
: "failed")
<< std::endl;
}
void print_http_begin_request_headers(int64_t stream_id) {
fprintf(outfile, "http: stream 0x%" PRIx64 " request headers started\n",
stream_id);
}
void print_http_begin_response_headers(int64_t stream_id) {
fprintf(outfile, "http: stream 0x%" PRIx64 " response headers started\n",
stream_id);
}
namespace {
void print_header(std::span<const uint8_t> name, std::span<const uint8_t> value,
uint8_t flags) {
fprintf(outfile, "[%.*s: %.*s]%s\n", static_cast<int>(name.size()),
name.data(), static_cast<int>(value.size()), value.data(),
(flags & NGHTTP3_NV_FLAG_NEVER_INDEX) ? "(sensitive)" : "");
}
} // namespace
namespace {
void print_header(const nghttp3_rcbuf *name, const nghttp3_rcbuf *value,
uint8_t flags) {
auto namebuf = nghttp3_rcbuf_get_buf(name);
auto valuebuf = nghttp3_rcbuf_get_buf(value);
print_header({namebuf.base, namebuf.len}, {valuebuf.base, valuebuf.len},
flags);
}
} // namespace
namespace {
void print_header(const nghttp3_nv &nv) {
print_header({nv.name, nv.namelen}, {nv.value, nv.valuelen}, nv.flags);
}
} // namespace
void print_http_header(int64_t stream_id, const nghttp3_rcbuf *name,
const nghttp3_rcbuf *value, uint8_t flags) {
fprintf(outfile, "http: stream 0x%" PRIx64 " ", stream_id);
print_header(name, value, flags);
}
void print_http_end_headers(int64_t stream_id) {
fprintf(outfile, "http: stream 0x%" PRIx64 " headers ended\n", stream_id);
}
void print_http_data(int64_t stream_id, std::span<const uint8_t> data) {
fprintf(outfile, "http: stream 0x%" PRIx64 " body %zu bytes\n", stream_id,
data.size());
util::hexdump(outfile, data.data(), data.size());
}
void print_http_begin_trailers(int64_t stream_id) {
fprintf(outfile, "http: stream 0x%" PRIx64 " trailers started\n", stream_id);
}
void print_http_end_trailers(int64_t stream_id) {
fprintf(outfile, "http: stream 0x%" PRIx64 " trailers ended\n", stream_id);
}
void print_http_request_headers(int64_t stream_id, const nghttp3_nv *nva,
size_t nvlen) {
fprintf(outfile, "http: stream 0x%" PRIx64 " submit request headers\n",
stream_id);
for (size_t i = 0; i < nvlen; ++i) {
auto &nv = nva[i];
print_header(nv);
}
}
void print_http_response_headers(int64_t stream_id, const nghttp3_nv *nva,
size_t nvlen) {
fprintf(outfile, "http: stream 0x%" PRIx64 " submit response headers\n",
stream_id);
for (size_t i = 0; i < nvlen; ++i) {
auto &nv = nva[i];
print_header(nv);
}
}
void print_http_settings(const nghttp3_settings *settings) {
fprintf(outfile,
"http: remote settings\n"
"http: SETTINGS_MAX_FIELD_SECTION_SIZE=%" PRIu64 "\n"
"http: SETTINGS_QPACK_MAX_TABLE_CAPACITY=%zu\n"
"http: SETTINGS_QPACK_BLOCKED_STREAMS=%zu\n"
"http: SETTINGS_ENABLE_CONNECT_PROTOCOL=%d\n"
"http: SETTINGS_H3_DATAGRAM=%d\n",
settings->max_field_section_size, settings->qpack_max_dtable_capacity,
settings->qpack_blocked_streams, settings->enable_connect_protocol,
settings->h3_datagram);
}
void print_http_origin(const uint8_t *origin, size_t originlen) {
fprintf(outfile, "http: origin [%.*s]\n", static_cast<int>(originlen),
origin);
}
void print_http_end_origin() { fprintf(outfile, "http: origin ended\n"); }
std::string_view secret_title(ngtcp2_encryption_level level) {
switch (level) {
case NGTCP2_ENCRYPTION_LEVEL_0RTT:
return "early_traffic"sv;
case NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE:
return "handshake_traffic"sv;
case NGTCP2_ENCRYPTION_LEVEL_1RTT:
return "application_traffic"sv;
default:
assert(0);
abort();
}
}
} // namespace debug
} // namespace ngtcp2

131
deps/ngtcp2/ngtcp2/examples/debug.h vendored Normal file
View File

@ -0,0 +1,131 @@
/*
* ngtcp2
*
* Copyright (c) 2017 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef DEBUG_H
#define DEBUG_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#ifndef __STDC_FORMAT_MACROS
// For travis and PRIu64
# define __STDC_FORMAT_MACROS
#endif // !defined(__STDC_FORMAT_MACROS)
#include <cinttypes>
#include <string_view>
#include <span>
#include <ngtcp2/ngtcp2.h>
#include <nghttp3/nghttp3.h>
namespace ngtcp2 {
namespace debug {
int handshake_completed(ngtcp2_conn *conn, void *user_data);
int handshake_confirmed(ngtcp2_conn *conn, void *user_data);
bool packet_lost(double prob);
void print_crypto_data(ngtcp2_encryption_level encryption_level,
std::span<const uint8_t> data);
void print_stream_data(int64_t stream_id, std::span<const uint8_t> data);
void print_initial_secret(std::span<const uint8_t> data);
void print_client_in_secret(std::span<const uint8_t> data);
void print_server_in_secret(std::span<const uint8_t> data);
void print_handshake_secret(std::span<const uint8_t> data);
void print_client_hs_secret(std::span<const uint8_t> data);
void print_server_hs_secret(std::span<const uint8_t> data);
void print_client_0rtt_secret(std::span<const uint8_t> data);
void print_client_1rtt_secret(std::span<const uint8_t> data);
void print_server_1rtt_secret(std::span<const uint8_t> data);
void print_client_pp_key(std::span<const uint8_t> data);
void print_server_pp_key(std::span<const uint8_t> data);
void print_client_pp_iv(std::span<const uint8_t> data);
void print_server_pp_iv(std::span<const uint8_t> data);
void print_client_pp_hp(std::span<const uint8_t> data);
void print_server_pp_hp(std::span<const uint8_t> data);
void print_secrets(std::span<const uint8_t> secret,
std::span<const uint8_t> key, std::span<const uint8_t> iv,
std::span<const uint8_t> hp);
void print_secrets(std::span<const uint8_t> secret,
std::span<const uint8_t> key, std::span<const uint8_t> iv);
void print_hp_mask(std::span<const uint8_t> mask,
std::span<const uint8_t> sample);
void log_printf(void *user_data, const char *fmt, ...);
void path_validation(const ngtcp2_path *path,
ngtcp2_path_validation_result res);
void print_http_begin_request_headers(int64_t stream_id);
void print_http_begin_response_headers(int64_t stream_id);
void print_http_header(int64_t stream_id, const nghttp3_rcbuf *name,
const nghttp3_rcbuf *value, uint8_t flags);
void print_http_end_headers(int64_t stream_id);
void print_http_data(int64_t stream_id, std::span<const uint8_t> data);
void print_http_begin_trailers(int64_t stream_id);
void print_http_end_trailers(int64_t stream_id);
void print_http_request_headers(int64_t stream_id, const nghttp3_nv *nva,
size_t nvlen);
void print_http_response_headers(int64_t stream_id, const nghttp3_nv *nva,
size_t nvlen);
void print_http_settings(const nghttp3_settings *settings);
void print_http_origin(const uint8_t *origin, size_t originlen);
void print_http_end_origin();
std::string_view secret_title(ngtcp2_encryption_level level);
} // namespace debug
} // namespace ngtcp2
#endif // !defined(DEBUG_H)

View File

@ -0,0 +1,49 @@
/*
* ngtcp2
*
* Copyright (c) 2018 ngtcp2 contributors
* Copyright (c) 2013 nghttp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include "munit.h"
// include test cases' include files here
#include "util_test.h"
#include "siphash_test.h"
int main(int argc, char *argv[]) {
const MunitSuite suites[] = {
ngtcp2::util_suite,
ngtcp2::siphash_suite,
{},
};
const MunitSuite suite = {
.prefix = "",
.suites = suites,
.iterations = 1,
};
return munit_suite_main(&suite, nullptr, argc, argv);
}

View File

@ -0,0 +1,699 @@
/*
* ngtcp2
*
* Copyright (c) 2021-2022 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* defined(HAVE_CONFIG_H) */
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <ngtcp2/ngtcp2_crypto_gnutls.h>
#include <gnutls/crypto.h>
#include <gnutls/gnutls.h>
#include <ev.h>
#define REMOTE_HOST "127.0.0.1"
#define REMOTE_PORT "4433"
#define ALPN "hq-interop"
#define MESSAGE "GET /\r\n"
/*
* Example 1: Handshake with www.google.com
*
* #define REMOTE_HOST "www.google.com"
* #define REMOTE_PORT "443"
* #define ALPN "h3"
*
* and undefine MESSAGE macro.
*/
static uint64_t timestamp(void) {
struct timespec tp;
if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) {
fprintf(stderr, "clock_gettime: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
return (uint64_t)tp.tv_sec * NGTCP2_SECONDS + (uint64_t)tp.tv_nsec;
}
static int create_sock(struct sockaddr *addr, socklen_t *paddrlen,
const char *host, const char *port) {
struct addrinfo hints = {0};
struct addrinfo *res, *rp;
int rv;
int fd = -1;
hints.ai_flags = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
rv = getaddrinfo(host, port, &hints, &res);
if (rv != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return -1;
}
for (rp = res; rp; rp = rp->ai_next) {
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (fd == -1) {
continue;
}
break;
}
if (fd == -1) {
goto end;
}
*paddrlen = rp->ai_addrlen;
memcpy(addr, rp->ai_addr, rp->ai_addrlen);
end:
freeaddrinfo(res);
return fd;
}
static int connect_sock(struct sockaddr *local_addr, socklen_t *plocal_addrlen,
int fd, const struct sockaddr *remote_addr,
size_t remote_addrlen) {
socklen_t len;
if (connect(fd, remote_addr, (socklen_t)remote_addrlen) != 0) {
fprintf(stderr, "connect: %s\n", strerror(errno));
return -1;
}
len = *plocal_addrlen;
if (getsockname(fd, local_addr, &len) == -1) {
fprintf(stderr, "getsockname: %s\n", strerror(errno));
return -1;
}
*plocal_addrlen = len;
return 0;
}
struct client {
ngtcp2_crypto_conn_ref conn_ref;
int fd;
struct sockaddr_storage local_addr;
socklen_t local_addrlen;
gnutls_certificate_credentials_t cred;
gnutls_session_t session;
ngtcp2_conn *conn;
struct {
int64_t stream_id;
const uint8_t *data;
size_t datalen;
size_t nwrite;
} stream;
ngtcp2_ccerr last_error;
ev_io rev;
ev_timer timer;
};
static int hook_func(gnutls_session_t session, unsigned int htype,
unsigned when, unsigned int incoming,
const gnutls_datum_t *msg) {
(void)session;
(void)htype;
(void)when;
(void)incoming;
(void)msg;
/* we could save session data here */
return 0;
}
static int numeric_host_family(const char *hostname, int family) {
uint8_t dst[sizeof(struct in6_addr)];
return inet_pton(family, hostname, dst) == 1;
}
static int numeric_host(const char *hostname) {
return numeric_host_family(hostname, AF_INET) ||
numeric_host_family(hostname, AF_INET6);
}
static const char priority[] =
"NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:"
"+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:+GROUP-X25519:"
"+GROUP-SECP384R1:"
"+GROUP-SECP521R1:%DISABLE_TLS13_COMPAT_MODE";
static const gnutls_datum_t alpn = {(uint8_t *)ALPN, sizeof(ALPN) - 1};
static int client_gnutls_init(struct client *c) {
int rv = gnutls_certificate_allocate_credentials(&c->cred);
if (rv == 0)
rv = gnutls_certificate_set_x509_system_trust(c->cred);
if (rv < 0) {
fprintf(stderr, "cred init failed: %d: %s\n", rv, gnutls_strerror(rv));
return -1;
}
rv = gnutls_init(&c->session, GNUTLS_CLIENT | GNUTLS_ENABLE_EARLY_DATA |
GNUTLS_NO_END_OF_EARLY_DATA);
if (rv != 0) {
fprintf(stderr, "gnutls_init: %s\n", gnutls_strerror(rv));
return -1;
}
if (ngtcp2_crypto_gnutls_configure_client_session(c->session) != 0) {
fprintf(stderr, "ngtcp2_crypto_gnutls_configure_client_session failed\n");
return -1;
}
rv = gnutls_priority_set_direct(c->session, priority, NULL);
if (rv != 0) {
fprintf(stderr, "gnutls_priority_set_direct: %s\n", gnutls_strerror(rv));
return -1;
}
gnutls_handshake_set_hook_function(c->session, GNUTLS_HANDSHAKE_ANY,
GNUTLS_HOOK_POST, hook_func);
gnutls_session_set_ptr(c->session, &c->conn_ref);
rv = gnutls_credentials_set(c->session, GNUTLS_CRD_CERTIFICATE, c->cred);
if (rv != 0) {
fprintf(stderr, "gnutls_credentials_set: %s\n", gnutls_strerror(rv));
return -1;
}
gnutls_alpn_set_protocols(c->session, &alpn, 1, GNUTLS_ALPN_MANDATORY);
if (!numeric_host(REMOTE_HOST)) {
gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, REMOTE_HOST,
strlen(REMOTE_HOST));
} else {
gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, "localhost",
strlen("localhost"));
}
return 0;
}
static void rand_cb(uint8_t *dest, size_t destlen,
const ngtcp2_rand_ctx *rand_ctx) {
(void)rand_ctx;
(void)gnutls_rnd(GNUTLS_RND_RANDOM, dest, destlen);
}
static int get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid,
uint8_t *token, size_t cidlen,
void *user_data) {
(void)conn;
(void)user_data;
if (gnutls_rnd(GNUTLS_RND_RANDOM, cid->data, cidlen) != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
cid->datalen = cidlen;
if (gnutls_rnd(GNUTLS_RND_RANDOM, token, NGTCP2_STATELESS_RESET_TOKENLEN) !=
0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
static int extend_max_local_streams_bidi(ngtcp2_conn *conn,
uint64_t max_streams,
void *user_data) {
#ifdef MESSAGE
struct client *c = user_data;
int rv;
int64_t stream_id;
(void)max_streams;
if (c->stream.stream_id != -1) {
return 0;
}
rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL);
if (rv != 0) {
return 0;
}
c->stream.stream_id = stream_id;
c->stream.data = (const uint8_t *)MESSAGE;
c->stream.datalen = sizeof(MESSAGE) - 1;
return 0;
#else /* !defined(MESSAGE) */
(void)conn;
(void)max_streams;
(void)user_data;
return 0;
#endif /* !defined(MESSAGE) */
}
static void log_printf(void *user_data, const char *fmt, ...) {
va_list ap;
(void)user_data;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
}
static int client_quic_init(struct client *c,
const struct sockaddr *remote_addr,
socklen_t remote_addrlen,
const struct sockaddr *local_addr,
socklen_t local_addrlen) {
ngtcp2_path path = {
.local =
{
.addr = (struct sockaddr *)local_addr,
.addrlen = local_addrlen,
},
.remote =
{
.addr = (struct sockaddr *)remote_addr,
.addrlen = remote_addrlen,
},
};
ngtcp2_callbacks callbacks = {
.client_initial = ngtcp2_crypto_client_initial_cb,
.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb,
.encrypt = ngtcp2_crypto_encrypt_cb,
.decrypt = ngtcp2_crypto_decrypt_cb,
.hp_mask = ngtcp2_crypto_hp_mask_cb,
.recv_retry = ngtcp2_crypto_recv_retry_cb,
.extend_max_local_streams_bidi = extend_max_local_streams_bidi,
.rand = rand_cb,
.get_new_connection_id = get_new_connection_id_cb,
.update_key = ngtcp2_crypto_update_key_cb,
.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb,
.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb,
.version_negotiation = ngtcp2_crypto_version_negotiation_cb,
};
ngtcp2_cid dcid, scid;
ngtcp2_settings settings;
ngtcp2_transport_params params;
int rv;
dcid.datalen = NGTCP2_MIN_INITIAL_DCIDLEN;
if (gnutls_rnd(GNUTLS_RND_RANDOM, dcid.data, dcid.datalen) != 0) {
fprintf(stderr, "gnutls_rnd failed\n");
return -1;
}
scid.datalen = 8;
if (gnutls_rnd(GNUTLS_RND_RANDOM, scid.data, scid.datalen) != 0) {
fprintf(stderr, "gnutls_rnd failed\n");
return -1;
}
ngtcp2_settings_default(&settings);
settings.initial_ts = timestamp();
settings.log_printf = log_printf;
ngtcp2_transport_params_default(&params);
params.initial_max_streams_uni = 3;
params.initial_max_stream_data_bidi_local = 128 * 1024;
params.initial_max_data = 1024 * 1024;
rv =
ngtcp2_conn_client_new(&c->conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1,
&callbacks, &settings, &params, NULL, c);
if (rv != 0) {
fprintf(stderr, "ngtcp2_conn_client_new: %s\n", ngtcp2_strerror(rv));
return -1;
}
ngtcp2_conn_set_tls_native_handle(c->conn, c->session);
return 0;
}
static int client_read(struct client *c) {
uint8_t buf[65536];
struct sockaddr_storage addr;
struct iovec iov = {
.iov_base = buf,
.iov_len = sizeof(buf),
};
struct msghdr msg = {0};
ssize_t nread;
ngtcp2_path path;
ngtcp2_pkt_info pi = {0};
int rv;
msg.msg_name = &addr;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
for (;;) {
msg.msg_namelen = sizeof(addr);
nread = recvmsg(c->fd, &msg, MSG_DONTWAIT);
if (nread == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
fprintf(stderr, "recvmsg: %s\n", strerror(errno));
}
break;
}
path.local.addrlen = c->local_addrlen;
path.local.addr = (struct sockaddr *)&c->local_addr;
path.remote.addrlen = msg.msg_namelen;
path.remote.addr = msg.msg_name;
rv = ngtcp2_conn_read_pkt(c->conn, &path, &pi, buf, (size_t)nread,
timestamp());
if (rv != 0) {
fprintf(stderr, "ngtcp2_conn_read_pkt: %s\n", ngtcp2_strerror(rv));
if (!c->last_error.error_code) {
if (rv == NGTCP2_ERR_CRYPTO) {
ngtcp2_ccerr_set_tls_alert(
&c->last_error, ngtcp2_conn_get_tls_alert(c->conn), NULL, 0);
} else {
ngtcp2_ccerr_set_liberr(&c->last_error, rv, NULL, 0);
}
}
return -1;
}
}
return 0;
}
static int client_send_packet(struct client *c, const uint8_t *data,
size_t datalen) {
struct iovec iov = {
.iov_base = (uint8_t *)data,
.iov_len = datalen,
};
struct msghdr msg = {0};
ssize_t nwrite;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
do {
nwrite = sendmsg(c->fd, &msg, 0);
} while (nwrite == -1 && errno == EINTR);
if (nwrite == -1) {
fprintf(stderr, "sendmsg: %s\n", strerror(errno));
return -1;
}
return 0;
}
static size_t client_get_message(struct client *c, int64_t *pstream_id,
int *pfin, ngtcp2_vec *datav,
size_t datavcnt) {
if (datavcnt == 0) {
return 0;
}
if (c->stream.stream_id != -1 && c->stream.nwrite < c->stream.datalen) {
*pstream_id = c->stream.stream_id;
*pfin = 1;
datav->base = (uint8_t *)c->stream.data + c->stream.nwrite;
datav->len = c->stream.datalen - c->stream.nwrite;
return 1;
}
*pstream_id = -1;
*pfin = 0;
datav->base = NULL;
datav->len = 0;
return 0;
}
static int client_write_streams(struct client *c) {
ngtcp2_tstamp ts = timestamp();
ngtcp2_pkt_info pi;
ngtcp2_ssize nwrite;
uint8_t buf[1452];
ngtcp2_path_storage ps;
ngtcp2_vec datav;
size_t datavcnt;
int64_t stream_id;
ngtcp2_ssize wdatalen;
uint32_t flags;
int fin;
ngtcp2_path_storage_zero(&ps);
for (;;) {
datavcnt = client_get_message(c, &stream_id, &fin, &datav, 1);
flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
if (fin) {
flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
}
nwrite = ngtcp2_conn_writev_stream(c->conn, &ps.path, &pi, buf, sizeof(buf),
&wdatalen, flags, stream_id, &datav,
datavcnt, ts);
if (nwrite < 0) {
switch (nwrite) {
case NGTCP2_ERR_WRITE_MORE:
c->stream.nwrite += (size_t)wdatalen;
continue;
default:
fprintf(stderr, "ngtcp2_conn_writev_stream: %s\n",
ngtcp2_strerror((int)nwrite));
ngtcp2_ccerr_set_liberr(&c->last_error, (int)nwrite, NULL, 0);
return -1;
}
}
if (nwrite == 0) {
return 0;
}
if (wdatalen > 0) {
c->stream.nwrite += (size_t)wdatalen;
}
if (client_send_packet(c, buf, (size_t)nwrite) != 0) {
break;
}
}
return 0;
}
static int client_write(struct client *c) {
ngtcp2_tstamp expiry, now;
ev_tstamp t;
if (client_write_streams(c) != 0) {
return -1;
}
expiry = ngtcp2_conn_get_expiry(c->conn);
now = timestamp();
t = expiry < now ? 1e-9 : (ev_tstamp)(expiry - now) / NGTCP2_SECONDS;
c->timer.repeat = t;
ev_timer_again(EV_DEFAULT, &c->timer);
return 0;
}
static int client_handle_expiry(struct client *c) {
int rv = ngtcp2_conn_handle_expiry(c->conn, timestamp());
if (rv != 0) {
fprintf(stderr, "ngtcp2_conn_handle_expiry: %s\n", ngtcp2_strerror(rv));
return -1;
}
return 0;
}
static void client_close(struct client *c) {
ngtcp2_ssize nwrite;
ngtcp2_pkt_info pi;
ngtcp2_path_storage ps;
uint8_t buf[1280];
if (ngtcp2_conn_in_closing_period(c->conn) ||
ngtcp2_conn_in_draining_period(c->conn)) {
goto fin;
}
ngtcp2_path_storage_zero(&ps);
nwrite = ngtcp2_conn_write_connection_close(
c->conn, &ps.path, &pi, buf, sizeof(buf), &c->last_error, timestamp());
if (nwrite < 0) {
fprintf(stderr, "ngtcp2_conn_write_connection_close: %s\n",
ngtcp2_strerror((int)nwrite));
goto fin;
}
client_send_packet(c, buf, (size_t)nwrite);
fin:
ev_break(EV_DEFAULT, EVBREAK_ALL);
}
static void read_cb(struct ev_loop *loop, ev_io *w, int revents) {
struct client *c = w->data;
(void)loop;
(void)revents;
if (client_read(c) != 0) {
client_close(c);
return;
}
if (client_write(c) != 0) {
client_close(c);
}
}
static void timer_cb(struct ev_loop *loop, ev_timer *w, int revents) {
struct client *c = w->data;
(void)loop;
(void)revents;
if (client_handle_expiry(c) != 0) {
client_close(c);
return;
}
if (client_write(c) != 0) {
client_close(c);
}
}
static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
struct client *c = conn_ref->user_data;
return c->conn;
}
static int client_init(struct client *c) {
struct sockaddr_storage remote_addr, local_addr;
socklen_t remote_addrlen, local_addrlen = sizeof(local_addr);
memset(c, 0, sizeof(*c));
ngtcp2_ccerr_default(&c->last_error);
c->fd = create_sock((struct sockaddr *)&remote_addr, &remote_addrlen,
REMOTE_HOST, REMOTE_PORT);
if (c->fd == -1) {
return -1;
}
if (connect_sock((struct sockaddr *)&local_addr, &local_addrlen, c->fd,
(struct sockaddr *)&remote_addr, remote_addrlen) != 0) {
return -1;
}
memcpy(&c->local_addr, &local_addr, sizeof(c->local_addr));
c->local_addrlen = local_addrlen;
if (client_gnutls_init(c) != 0) {
return -1;
}
if (client_quic_init(c, (struct sockaddr *)&remote_addr, remote_addrlen,
(struct sockaddr *)&local_addr, local_addrlen) != 0) {
return -1;
}
c->stream.stream_id = -1;
c->conn_ref.get_conn = get_conn;
c->conn_ref.user_data = c;
ev_io_init(&c->rev, read_cb, c->fd, EV_READ);
c->rev.data = c;
ev_io_start(EV_DEFAULT, &c->rev);
ev_timer_init(&c->timer, timer_cb, 0., 0.);
c->timer.data = c;
return 0;
}
static void client_free(struct client *c) {
ngtcp2_conn_del(c->conn);
gnutls_deinit(c->session);
gnutls_certificate_free_credentials(c->cred);
}
int main(void) {
struct client c;
if (client_init(&c) != 0) {
exit(EXIT_FAILURE);
}
if (client_write(&c) != 0) {
exit(EXIT_FAILURE);
}
ev_run(EV_DEFAULT, 0);
client_free(&c);
return 0;
}

2867
deps/ngtcp2/ngtcp2/examples/h09client.cc vendored Normal file

File diff suppressed because it is too large Load Diff

202
deps/ngtcp2/ngtcp2/examples/h09client.h vendored Normal file
View File

@ -0,0 +1,202 @@
/*
* ngtcp2
*
* Copyright (c) 2017 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef H09CLIENT_H
#define H09CLIENT_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <vector>
#include <deque>
#include <unordered_map>
#include <string_view>
#include <memory>
#include <set>
#include <span>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <nghttp3/nghttp3.h>
#include <ev.h>
#include "client_base.h"
#include "tls_client_context.h"
#include "tls_client_session.h"
#include "network.h"
#include "shared.h"
#include "template.h"
using namespace ngtcp2;
struct Stream {
Stream(const Request &req, int64_t stream_id);
~Stream();
int open_file(const std::string_view &path);
Request req;
int64_t stream_id;
int fd;
std::string rawreqbuf;
nghttp3_buf reqbuf;
};
struct StreamIDLess {
constexpr bool operator()(const Stream *lhs, const Stream *rhs) const {
return lhs->stream_id < rhs->stream_id;
}
};
class Client;
struct Endpoint {
Address addr;
ev_io rev;
Client *client;
int fd;
};
class Client : public ClientBase {
public:
Client(struct ev_loop *loop, uint32_t client_chosen_version,
uint32_t original_version);
~Client();
int init(int fd, const Address &local_addr, const Address &remote_addr,
const char *addr, const char *port, TLSClientContext &tls_ctx);
void disconnect();
int on_read(const Endpoint &ep);
int on_write();
int write_streams();
int feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen,
const ngtcp2_pkt_info *pi, std::span<const uint8_t> data);
int handle_expiry();
void update_timer();
int handshake_completed();
int handshake_confirmed();
void recv_version_negotiation(const uint32_t *sv, size_t nsv);
int send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr,
unsigned int ecn, std::span<const uint8_t> data);
std::pair<std::span<const uint8_t>, int>
send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr,
unsigned int ecn, std::span<const uint8_t> data, size_t gso_size);
int send_packet_or_blocked(const ngtcp2_path &path, unsigned int ecn,
std::span<const uint8_t> data, size_t gso_size);
int on_stream_close(int64_t stream_id, uint64_t app_error_code);
int on_extend_max_streams();
int handle_error();
int make_stream_early();
int change_local_addr();
void start_change_local_addr_timer();
int update_key(uint8_t *rx_secret, uint8_t *tx_secret,
ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
const uint8_t *current_rx_secret,
const uint8_t *current_tx_secret, size_t secretlen);
int initiate_key_update();
void start_key_update_timer();
void start_delay_stream_timer();
int select_preferred_address(Address &selected_addr,
const ngtcp2_preferred_addr *paddr);
std::optional<Endpoint *> endpoint_for(const Address &remote_addr);
void set_remote_addr(const ngtcp2_addr &remote_addr);
int submit_http_request(Stream *stream);
int recv_stream_data(uint32_t flags, int64_t stream_id,
std::span<const uint8_t> data);
int acked_stream_data_offset(int64_t stream_id, uint64_t offset,
uint64_t datalen);
int extend_max_stream_data(int64_t stream_id, uint64_t max_data);
void write_qlog(const void *data, size_t datalen);
void on_send_blocked(const ngtcp2_path &path, unsigned int ecn,
std::span<const uint8_t> data, size_t gso_size);
void start_wev_endpoint(const Endpoint &ep);
int send_blocked_packet();
ngtcp2_ssize write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest,
size_t destlen, ngtcp2_tstamp ts);
const std::vector<uint32_t> &get_offered_versions() const;
void early_data_rejected();
bool should_exit() const;
private:
std::vector<Endpoint> endpoints_;
Address remote_addr_;
ev_io wev_;
ev_timer timer_;
ev_timer change_local_addr_timer_;
ev_timer key_update_timer_;
ev_timer delay_stream_timer_;
ev_signal sigintev_;
struct ev_loop *loop_;
std::unordered_map<int64_t, std::unique_ptr<Stream>> streams_;
std::set<Stream *, StreamIDLess> sendq_;
std::vector<uint32_t> offered_versions_;
// addr_ is the server host address.
const char *addr_;
// port_ is the server port.
const char *port_;
// nstreams_done_ is the number of streams opened.
size_t nstreams_done_;
// nstreams_closed_ is the number of streams get closed.
size_t nstreams_closed_;
// nkey_update_ is the number of key update occurred.
size_t nkey_update_;
uint32_t client_chosen_version_;
uint32_t original_version_;
// early_data_ is true if client attempts to do 0RTT data transfer.
bool early_data_;
// handshake_confirmed_ gets true after handshake has been
// confirmed.
bool handshake_confirmed_;
bool no_gso_;
struct {
bool send_blocked;
// blocked field is effective only when send_blocked is true.
struct {
const Endpoint *endpoint;
Address remote_addr;
unsigned int ecn;
std::span<const uint8_t> data;
size_t gso_size;
} blocked;
std::array<uint8_t, 64_k> data;
} tx_;
};
#endif // !defined(H09CLIENT_H)

3294
deps/ngtcp2/ngtcp2/examples/h09server.cc vendored Normal file

File diff suppressed because it is too large Load Diff

257
deps/ngtcp2/ngtcp2/examples/h09server.h vendored Normal file
View File

@ -0,0 +1,257 @@
/*
* ngtcp2
*
* Copyright (c) 2017 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef H09SERVER_H
#define H09SERVER_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <vector>
#include <unordered_map>
#include <string>
#include <deque>
#include <string_view>
#include <memory>
#include <set>
#include <span>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <nghttp3/nghttp3.h>
#include <ev.h>
#include "server_base.h"
#include "tls_server_context.h"
#include "network.h"
#include "shared.h"
#include "util.h"
using namespace ngtcp2;
struct HTTPHeader {
HTTPHeader(const std::string_view &name, const std::string_view &value)
: name(name), value(value) {}
std::string_view name;
std::string_view value;
};
class Handler;
struct FileEntry;
struct Stream {
Stream(int64_t stream_id, Handler *handler);
int start_response();
std::pair<FileEntry, int> open_file(const std::string &path);
void map_file(const FileEntry &fe);
int send_status_response(unsigned int status_code);
int64_t stream_id;
Handler *handler;
// uri is request uri/path.
std::string uri;
std::string status_resp_body;
nghttp3_buf respbuf;
http_parser htp;
// eos gets true when one HTTP request message is seen.
bool eos;
};
struct StreamIDLess {
constexpr bool operator()(const Stream *lhs, const Stream *rhs) const {
return lhs->stream_id < rhs->stream_id;
}
};
class Server;
// Endpoint is a local endpoint.
struct Endpoint {
Address addr;
ev_io rev;
Server *server;
int fd;
};
class Handler : public HandlerBase {
public:
Handler(struct ev_loop *loop, Server *server);
~Handler();
int init(const Endpoint &ep, const Address &local_addr, const sockaddr *sa,
socklen_t salen, const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
const ngtcp2_cid *ocid, std::span<const uint8_t> token,
ngtcp2_token_type token_type, uint32_t version,
TLSServerContext &tls_ctx);
int on_read(const Endpoint &ep, const Address &local_addr, const sockaddr *sa,
socklen_t salen, const ngtcp2_pkt_info *pi,
std::span<const uint8_t> data);
int on_write();
int write_streams();
int feed_data(const Endpoint &ep, const Address &local_addr,
const sockaddr *sa, socklen_t salen, const ngtcp2_pkt_info *pi,
std::span<const uint8_t> data);
void update_timer();
int handle_expiry();
void signal_write();
int handshake_completed();
Server *server() const;
int recv_stream_data(uint32_t flags, int64_t stream_id,
std::span<const uint8_t> data);
int acked_stream_data_offset(int64_t stream_id, uint64_t offset,
uint64_t datalen);
uint32_t version() const;
void on_stream_open(int64_t stream_id);
int on_stream_close(int64_t stream_id, uint64_t app_error_code);
void start_draining_period();
int start_closing_period();
int handle_error();
int send_conn_close();
int send_conn_close(const Endpoint &ep, const Address &local_addr,
const sockaddr *sa, socklen_t salen,
const ngtcp2_pkt_info *pi, std::span<const uint8_t> data);
int update_key(uint8_t *rx_secret, uint8_t *tx_secret,
ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
const uint8_t *current_rx_secret,
const uint8_t *current_tx_secret, size_t secretlen);
Stream *find_stream(int64_t stream_id);
int extend_max_stream_data(int64_t stream_id, uint64_t max_data);
void shutdown_read(int64_t stream_id, uint64_t app_error_code);
void write_qlog(const void *data, size_t datalen);
void add_sendq(Stream *stream);
void on_send_blocked(const ngtcp2_path &path, unsigned int ecn,
std::span<const uint8_t> data, size_t gso_size);
void start_wev_endpoint(const Endpoint &ep);
int send_packet(const ngtcp2_path &path, unsigned int ecn,
std::span<const uint8_t> data, size_t gso_size);
int send_blocked_packet();
ngtcp2_ssize write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest,
size_t destlen, ngtcp2_tstamp ts);
private:
struct ev_loop *loop_;
Server *server_;
ev_io wev_;
ev_timer timer_;
FILE *qlog_;
ngtcp2_cid scid_;
std::unordered_map<int64_t, std::unique_ptr<Stream>> streams_;
std::set<Stream *, StreamIDLess> sendq_;
// conn_closebuf_ contains a packet which contains CONNECTION_CLOSE.
// This packet is repeatedly sent as a response to the incoming
// packet in draining period.
std::unique_ptr<Buffer> conn_closebuf_;
// nkey_update_ is the number of key update occurred.
size_t nkey_update_;
bool no_gso_;
struct {
size_t bytes_recv;
size_t bytes_sent;
size_t num_pkts_recv;
size_t next_pkts_recv;
} close_wait_;
struct {
bool send_blocked;
// blocked field is effective only when send_blocked is true.
struct {
const Endpoint *endpoint;
Address local_addr;
Address remote_addr;
unsigned int ecn;
std::span<const uint8_t> data;
size_t gso_size;
} blocked;
std::unique_ptr<uint8_t[]> data;
} tx_;
};
class Server {
public:
Server(struct ev_loop *loop, TLSServerContext &tls_ctx);
~Server();
int init(const char *addr, const char *port);
void disconnect();
void close();
int on_read(const Endpoint &ep);
void read_pkt(const Endpoint &ep, const Address &local_addr,
const sockaddr *sa, socklen_t salen, const ngtcp2_pkt_info *pi,
std::span<const uint8_t> data);
int send_version_negotiation(uint32_t version, std::span<const uint8_t> dcid,
std::span<const uint8_t> scid,
const Endpoint &ep, const Address &local_addr,
const sockaddr *sa, socklen_t salen);
int send_retry(const ngtcp2_pkt_hd *chd, const Endpoint &ep,
const Address &local_addr, const sockaddr *sa, socklen_t salen,
size_t max_pktlen);
int send_stateless_connection_close(const ngtcp2_pkt_hd *chd,
const Endpoint &ep,
const Address &local_addr,
const sockaddr *sa, socklen_t salen);
int send_stateless_reset(size_t pktlen, std::span<const uint8_t> dcid,
const Endpoint &ep, const Address &local_addr,
const sockaddr *sa, socklen_t salen);
int verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
const sockaddr *sa, socklen_t salen);
int verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa,
socklen_t salen);
int send_packet(const Endpoint &ep, const ngtcp2_addr &local_addr,
const ngtcp2_addr &remote_addr, unsigned int ecn,
std::span<const uint8_t> data);
std::pair<std::span<const uint8_t>, int>
send_packet(const Endpoint &ep, bool &no_gso, const ngtcp2_addr &local_addr,
const ngtcp2_addr &remote_addr, unsigned int ecn,
std::span<const uint8_t> data, size_t gso_size);
void remove(const Handler *h);
void associate_cid(const ngtcp2_cid *cid, Handler *h);
void dissociate_cid(const ngtcp2_cid *cid);
void on_stateless_reset_regen();
private:
std::unordered_map<ngtcp2_cid, Handler *> handlers_;
struct ev_loop *loop_;
std::vector<Endpoint> endpoints_;
TLSServerContext &tls_ctx_;
ev_signal sigintev_;
ev_timer stateless_reset_regen_timer_;
size_t stateless_reset_bucket_;
};
#endif // !defined(H09SERVER_H)

140
deps/ngtcp2/ngtcp2/examples/http.cc vendored Normal file
View File

@ -0,0 +1,140 @@
/*
* ngtcp2
*
* Copyright (c) 2017 ngtcp2 contributors
* Copyright (c) 2012 nghttp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "http.h"
using namespace std::literals;
namespace ngtcp2 {
namespace http {
std::string_view get_reason_phrase(unsigned int status_code) {
switch (status_code) {
case 100:
return "Continue"sv;
case 101:
return "Switching Protocols"sv;
case 200:
return "OK"sv;
case 201:
return "Created"sv;
case 202:
return "Accepted"sv;
case 203:
return "Non-Authoritative Information"sv;
case 204:
return "No Content"sv;
case 205:
return "Reset Content"sv;
case 206:
return "Partial Content"sv;
case 300:
return "Multiple Choices"sv;
case 301:
return "Moved Permanently"sv;
case 302:
return "Found"sv;
case 303:
return "See Other"sv;
case 304:
return "Not Modified"sv;
case 305:
return "Use Proxy"sv;
// case 306: return "(Unused)"sv;
case 307:
return "Temporary Redirect"sv;
case 308:
return "Permanent Redirect"sv;
case 400:
return "Bad Request"sv;
case 401:
return "Unauthorized"sv;
case 402:
return "Payment Required"sv;
case 403:
return "Forbidden"sv;
case 404:
return "Not Found"sv;
case 405:
return "Method Not Allowed"sv;
case 406:
return "Not Acceptable"sv;
case 407:
return "Proxy Authentication Required"sv;
case 408:
return "Request Timeout"sv;
case 409:
return "Conflict"sv;
case 410:
return "Gone"sv;
case 411:
return "Length Required"sv;
case 412:
return "Precondition Failed"sv;
case 413:
return "Payload Too Large"sv;
case 414:
return "URI Too Long"sv;
case 415:
return "Unsupported Media Type"sv;
case 416:
return "Requested Range Not Satisfiable"sv;
case 417:
return "Expectation Failed"sv;
case 421:
return "Misdirected Request"sv;
case 426:
return "Upgrade Required"sv;
case 428:
return "Precondition Required"sv;
case 429:
return "Too Many Requests"sv;
case 431:
return "Request Header Fields Too Large"sv;
case 451:
return "Unavailable For Legal Reasons"sv;
case 500:
return "Internal Server Error"sv;
case 501:
return "Not Implemented"sv;
case 502:
return "Bad Gateway"sv;
case 503:
return "Service Unavailable"sv;
case 504:
return "Gateway Timeout"sv;
case 505:
return "HTTP Version Not Supported"sv;
case 511:
return "Network Authentication Required"sv;
default:
return ""sv;
}
}
} // namespace http
} // namespace ngtcp2

44
deps/ngtcp2/ngtcp2/examples/http.h vendored Normal file
View File

@ -0,0 +1,44 @@
/*
* ngtcp2
*
* Copyright (c) 2017 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef HTTP_H
#define HTTP_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <string_view>
namespace ngtcp2 {
namespace http {
std::string_view get_reason_phrase(unsigned int status_code);
} // namespace http
} // namespace ngtcp2
#endif // !defined(HTTP_H)

80
deps/ngtcp2/ngtcp2/examples/network.h vendored Normal file
View File

@ -0,0 +1,80 @@
/*
* ngtcp2
*
* Copyright (c) 2017 ngtcp2 contributors
* Copyright (c) 2016 nghttp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef NETWORK_H
#define NETWORK_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <sys/types.h>
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h>
#endif // defined(HAVE_SYS_SOCKET_H)
#include <sys/un.h>
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif // defined(HAVE_NETINET_IN_H)
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif // defined(HAVE_ARPA_INET_H)
#include <array>
#include <ngtcp2/ngtcp2.h>
namespace ngtcp2 {
enum network_error {
NETWORK_ERR_OK = 0,
NETWORK_ERR_FATAL = -10,
NETWORK_ERR_SEND_BLOCKED = -11,
NETWORK_ERR_CLOSE_WAIT = -12,
NETWORK_ERR_RETRY = -13,
NETWORK_ERR_DROP_CONN = -14,
};
union in_addr_union {
in_addr in;
in6_addr in6;
};
union sockaddr_union {
sockaddr_storage storage;
sockaddr sa;
sockaddr_in6 in6;
sockaddr_in in;
};
struct Address {
socklen_t len;
union sockaddr_union su;
uint32_t ifindex;
};
} // namespace ngtcp2
#endif // !defined(NETWORK_H)

4054
deps/ngtcp2/ngtcp2/examples/server.cc vendored Normal file

File diff suppressed because it is too large Load Diff

276
deps/ngtcp2/ngtcp2/examples/server.h vendored Normal file
View File

@ -0,0 +1,276 @@
/*
* ngtcp2
*
* Copyright (c) 2017 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef SERVER_H
#define SERVER_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <vector>
#include <unordered_map>
#include <string>
#include <deque>
#include <string_view>
#include <memory>
#include <span>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <nghttp3/nghttp3.h>
#include <ev.h>
#include "server_base.h"
#include "tls_server_context.h"
#include "network.h"
#include "shared.h"
#include "util.h"
using namespace ngtcp2;
struct HTTPHeader {
HTTPHeader(const std::string_view &name, const std::string_view &value)
: name(name), value(value) {}
std::string_view name;
std::string_view value;
};
class Handler;
struct FileEntry;
struct Stream {
Stream(int64_t stream_id, Handler *handler);
int start_response(nghttp3_conn *conn);
std::pair<FileEntry, int> open_file(const std::string &path);
void map_file(const FileEntry &fe);
int send_status_response(nghttp3_conn *conn, unsigned int status_code,
const std::vector<HTTPHeader> &extra_headers = {});
int send_redirect_response(nghttp3_conn *conn, unsigned int status_code,
const std::string_view &path);
int64_t find_dyn_length(const std::string_view &path);
void http_acked_stream_data(uint64_t datalen);
int64_t stream_id;
Handler *handler;
// uri is request uri/path.
std::string uri;
std::string method;
std::string authority;
std::string status_resp_body;
// data is a pointer to the memory which maps file denoted by fd.
uint8_t *data;
// datalen is the length of mapped file by data.
uint64_t datalen;
// dynresp is true if dynamic data response is enabled.
bool dynresp;
// dyndataleft is the number of dynamic data left to send.
uint64_t dyndataleft;
// dynbuflen is the number of bytes in-flight.
uint64_t dynbuflen;
};
class Server;
// Endpoint is a local endpoint.
struct Endpoint {
Address addr;
ev_io rev;
Server *server;
int fd;
};
class Handler : public HandlerBase {
public:
Handler(struct ev_loop *loop, Server *server);
~Handler();
int init(const Endpoint &ep, const Address &local_addr, const sockaddr *sa,
socklen_t salen, const ngtcp2_cid *dcid, const ngtcp2_cid *scid,
const ngtcp2_cid *ocid, std::span<const uint8_t> token,
ngtcp2_token_type token_type, uint32_t version,
TLSServerContext &tls_ctx);
int on_read(const Endpoint &ep, const Address &local_addr, const sockaddr *sa,
socklen_t salen, const ngtcp2_pkt_info *pi,
std::span<const uint8_t> data);
int on_write();
int write_streams();
int feed_data(const Endpoint &ep, const Address &local_addr,
const sockaddr *sa, socklen_t salen, const ngtcp2_pkt_info *pi,
std::span<const uint8_t> data);
void update_timer();
int handle_expiry();
void signal_write();
int handshake_completed();
Server *server() const;
int recv_stream_data(uint32_t flags, int64_t stream_id,
std::span<const uint8_t> data);
int acked_stream_data_offset(int64_t stream_id, uint64_t datalen);
uint32_t version() const;
void on_stream_open(int64_t stream_id);
int on_stream_close(int64_t stream_id, uint64_t app_error_code);
void start_draining_period();
int start_closing_period();
int handle_error();
int send_conn_close();
int send_conn_close(const Endpoint &ep, const Address &local_addr,
const sockaddr *sa, socklen_t salen,
const ngtcp2_pkt_info *pi, std::span<const uint8_t> data);
int update_key(uint8_t *rx_secret, uint8_t *tx_secret,
ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv,
ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv,
const uint8_t *current_rx_secret,
const uint8_t *current_tx_secret, size_t secretlen);
int setup_httpconn();
void http_consume(int64_t stream_id, size_t nconsumed);
void extend_max_remote_streams_bidi(uint64_t max_streams);
Stream *find_stream(int64_t stream_id);
void http_begin_request_headers(int64_t stream_id);
void http_recv_request_header(Stream *stream, int32_t token,
nghttp3_rcbuf *name, nghttp3_rcbuf *value);
int http_end_request_headers(Stream *stream);
int http_end_stream(Stream *stream);
int start_response(Stream *stream);
int on_stream_reset(int64_t stream_id);
int on_stream_stop_sending(int64_t stream_id);
int extend_max_stream_data(int64_t stream_id, uint64_t max_data);
void shutdown_read(int64_t stream_id, uint64_t app_error_code);
void http_acked_stream_data(Stream *stream, uint64_t datalen);
void http_stream_close(int64_t stream_id, uint64_t app_error_code);
int http_stop_sending(int64_t stream_id, uint64_t app_error_code);
int http_reset_stream(int64_t stream_id, uint64_t app_error_code);
void write_qlog(const void *data, size_t datalen);
void on_send_blocked(const ngtcp2_path &path, unsigned int ecn,
std::span<const uint8_t> data, size_t gso_size);
void start_wev_endpoint(const Endpoint &ep);
int send_packet(const ngtcp2_path &path, unsigned int ecn,
std::span<const uint8_t> data, size_t gso_size);
int send_blocked_packet();
ngtcp2_ssize write_pkt(ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest,
size_t destlen, ngtcp2_tstamp ts);
private:
struct ev_loop *loop_;
Server *server_;
ev_io wev_;
ev_timer timer_;
FILE *qlog_;
ngtcp2_cid scid_;
nghttp3_conn *httpconn_;
std::unordered_map<int64_t, std::unique_ptr<Stream>> streams_;
// conn_closebuf_ contains a packet which contains CONNECTION_CLOSE.
// This packet is repeatedly sent as a response to the incoming
// packet in draining period.
std::unique_ptr<Buffer> conn_closebuf_;
// nkey_update_ is the number of key update occurred.
size_t nkey_update_;
bool no_gso_;
struct {
size_t bytes_recv;
size_t bytes_sent;
size_t num_pkts_recv;
size_t next_pkts_recv;
} close_wait_;
struct {
bool send_blocked;
// blocked field is effective only when send_blocked is true.
struct {
const Endpoint *endpoint;
Address local_addr;
Address remote_addr;
unsigned int ecn;
std::span<const uint8_t> data;
size_t gso_size;
} blocked;
std::unique_ptr<uint8_t[]> data;
} tx_;
};
class Server {
public:
Server(struct ev_loop *loop, TLSServerContext &tls_ctx);
~Server();
int init(const char *addr, const char *port);
void disconnect();
void close();
int on_read(const Endpoint &ep);
void read_pkt(const Endpoint &ep, const Address &local_addr,
const sockaddr *sa, socklen_t salen, const ngtcp2_pkt_info *pi,
std::span<const uint8_t> data);
int send_version_negotiation(uint32_t version, std::span<const uint8_t> dcid,
std::span<const uint8_t> scid,
const Endpoint &ep, const Address &local_addr,
const sockaddr *sa, socklen_t salen);
int send_retry(const ngtcp2_pkt_hd *chd, const Endpoint &ep,
const Address &local_addr, const sockaddr *sa, socklen_t salen,
size_t max_pktlen);
int send_stateless_connection_close(const ngtcp2_pkt_hd *chd,
const Endpoint &ep,
const Address &local_addr,
const sockaddr *sa, socklen_t salen);
int send_stateless_reset(size_t pktlen, std::span<const uint8_t> dcid,
const Endpoint &ep, const Address &local_addr,
const sockaddr *sa, socklen_t salen);
int verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
const sockaddr *sa, socklen_t salen);
int verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa,
socklen_t salen);
int send_packet(const Endpoint &ep, const ngtcp2_addr &local_addr,
const ngtcp2_addr &remote_addr, unsigned int ecn,
std::span<const uint8_t> data);
std::pair<std::span<const uint8_t>, int>
send_packet(const Endpoint &ep, bool &no_gso, const ngtcp2_addr &local_addr,
const ngtcp2_addr &remote_addr, unsigned int ecn,
std::span<const uint8_t> data, size_t gso_size);
void remove(const Handler *h);
void associate_cid(const ngtcp2_cid *cid, Handler *h);
void dissociate_cid(const ngtcp2_cid *cid);
void on_stateless_reset_regen();
private:
std::unordered_map<ngtcp2_cid, Handler *> handlers_;
struct ev_loop *loop_;
std::vector<Endpoint> endpoints_;
TLSServerContext &tls_ctx_;
ev_signal sigintev_;
ev_timer stateless_reset_regen_timer_;
size_t stateless_reset_bucket_;
};
#endif // !defined(SERVER_H)

View File

@ -0,0 +1,58 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "server_base.h"
#include <cassert>
#include <array>
#include <iostream>
#include "debug.h"
using namespace ngtcp2;
extern Config config;
Buffer::Buffer(const uint8_t *data, size_t datalen)
: buf{data, data + datalen}, begin(buf.data()), tail(begin + datalen) {}
Buffer::Buffer(size_t datalen) : buf(datalen), begin(buf.data()), tail(begin) {}
static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
return h->conn();
}
HandlerBase::HandlerBase() : conn_ref_{get_conn, this}, conn_(nullptr) {
ngtcp2_ccerr_default(&last_error_);
}
HandlerBase::~HandlerBase() {
if (conn_) {
ngtcp2_conn_del(conn_);
}
}
ngtcp2_conn *HandlerBase::conn() const { return conn_; }
ngtcp2_crypto_conn_ref *HandlerBase::conn_ref() { return &conn_ref_; }

View File

@ -0,0 +1,204 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef SERVER_BASE_H
#define SERVER_BASE_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <vector>
#include <deque>
#include <unordered_map>
#include <string>
#include <string_view>
#include <functional>
#include <span>
#include <ngtcp2/ngtcp2_crypto.h>
#include "tls_server_session.h"
#include "network.h"
#include "shared.h"
#include "template.h"
#include "util.h"
using namespace ngtcp2;
struct Config {
Address preferred_ipv4_addr;
Address preferred_ipv6_addr;
// tx_loss_prob is probability of losing outgoing packet.
double tx_loss_prob;
// rx_loss_prob is probability of losing incoming packet.
double rx_loss_prob;
// ciphers is the list of enabled ciphers.
const char *ciphers;
// groups is the list of supported groups.
const char *groups;
// htdocs is a root directory to serve documents.
std::string htdocs;
// mime_types_file is a path to "MIME media types and the
// extensions" file. Ubuntu mime-support package includes it in
// /etc/mime/types.
std::string_view mime_types_file;
// mime_types maps file extension to MIME media type.
std::unordered_map<std::string, std::string> mime_types;
// port is the port number which server listens on for incoming
// connections.
uint16_t port;
// quiet suppresses the output normally shown except for the error
// messages.
bool quiet;
// timeout is an idle timeout for QUIC connection.
ngtcp2_duration timeout;
// show_secret is true if transport secrets should be printed out.
bool show_secret;
// validate_addr is true if server requires address validation.
bool validate_addr;
// early_response is true if server starts sending response when it
// receives HTTP header fields without waiting for request body. If
// HTTP response data is written before receiving request body,
// STOP_SENDING is sent.
bool early_response;
// verify_client is true if server verifies client with X.509
// certificate based authentication.
bool verify_client;
// qlog_dir is the path to directory where qlog is stored.
std::string_view qlog_dir;
// no_quic_dump is true if hexdump of QUIC STREAM and CRYPTO data
// should be disabled.
bool no_quic_dump;
// no_http_dump is true if hexdump of HTTP response body should be
// disabled.
bool no_http_dump;
// max_data is the initial connection-level flow control window.
uint64_t max_data;
// max_stream_data_bidi_local is the initial stream-level flow
// control window for a bidirectional stream that the local endpoint
// initiates.
uint64_t max_stream_data_bidi_local;
// max_stream_data_bidi_remote is the initial stream-level flow
// control window for a bidirectional stream that the remote
// endpoint initiates.
uint64_t max_stream_data_bidi_remote;
// max_stream_data_uni is the initial stream-level flow control
// window for a unidirectional stream.
uint64_t max_stream_data_uni;
// max_streams_bidi is the number of the concurrent bidirectional
// streams.
uint64_t max_streams_bidi;
// max_streams_uni is the number of the concurrent unidirectional
// streams.
uint64_t max_streams_uni;
// max_window is the maximum connection-level flow control window
// size if auto-tuning is enabled.
uint64_t max_window;
// max_stream_window is the maximum stream-level flow control window
// size if auto-tuning is enabled.
uint64_t max_stream_window;
// max_dyn_length is the maximum length of dynamically generated
// response.
uint64_t max_dyn_length;
// static_secret is used to derive keying materials for Retry and
// Stateless Retry token.
std::array<uint8_t, 32> static_secret;
// cc_algo is the congestion controller algorithm.
ngtcp2_cc_algo cc_algo;
// initial_rtt is an initial RTT.
ngtcp2_duration initial_rtt;
// max_udp_payload_size is the maximum UDP payload size that server
// transmits.
size_t max_udp_payload_size;
// send_trailers controls whether server sends trailer fields or
// not.
bool send_trailers;
// handshake_timeout is the period of time before giving up QUIC
// connection establishment.
ngtcp2_duration handshake_timeout;
// preferred_versions includes QUIC versions in the order of
// preference. Server negotiates one of those versions if a client
// initially selects a less preferred version.
std::vector<uint32_t> preferred_versions;
// available_versions includes QUIC versions that are sent in
// available_versions field of version_information
// transport_parameter.
std::vector<uint32_t> available_versions;
// no_pmtud disables Path MTU Discovery.
bool no_pmtud;
// ack_thresh is the minimum number of the received ACK eliciting
// packets that triggers immediate acknowledgement.
size_t ack_thresh;
// initial_pkt_num is the initial packet number for each packet
// number space. If it is set to UINT32_MAX, it is chosen randomly.
uint32_t initial_pkt_num;
// pmtud_probes is the array of UDP datagram payload size to probes.
std::vector<uint16_t> pmtud_probes;
// ech_config contains server-side ECH configuration.
util::ECHServerConfig ech_config;
// origin_list contains a payload of ORIGIN frame.
std::optional<std::vector<uint8_t>> origin_list;
};
struct Buffer {
Buffer(const uint8_t *data, size_t datalen);
explicit Buffer(size_t datalen);
size_t size() const { return as_unsigned(tail - begin); }
size_t left() const { return as_unsigned(buf.data() + buf.size() - tail); }
uint8_t *const wpos() { return tail; }
std::span<const uint8_t> data() const { return {begin, size()}; }
void push(size_t len) { tail += len; }
void reset() { tail = begin; }
std::vector<uint8_t> buf;
// begin points to the beginning of the buffer. This might point to
// buf.data() if a buffer space is allocated by this object. It is
// also allowed to point to the external shared buffer.
uint8_t *begin;
// tail points to the position of the buffer where write should
// occur.
uint8_t *tail;
};
class HandlerBase {
public:
HandlerBase();
~HandlerBase();
ngtcp2_conn *conn() const;
TLSServerSession *get_session() { return &tls_session_; }
ngtcp2_crypto_conn_ref *conn_ref();
protected:
ngtcp2_crypto_conn_ref conn_ref_;
TLSServerSession tls_session_;
ngtcp2_conn *conn_;
ngtcp2_ccerr last_error_;
};
#endif // !defined(SERVER_BASE_H)

507
deps/ngtcp2/ngtcp2/examples/shared.cc vendored Normal file
View File

@ -0,0 +1,507 @@
/*
* ngtcp2
*
* Copyright (c) 2019 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "shared.h"
#include <nghttp3/nghttp3.h>
#include <cstring>
#include <cassert>
#include <iostream>
#include <unistd.h>
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif // defined(HAVE_NETINET_IN_H)
#ifdef HAVE_NETINET_UDP_H
# include <netinet/udp.h>
#endif // defined(HAVE_NETINET_UDP_H)
#ifdef HAVE_NETINET_IP_H
# include <netinet/ip.h>
#endif // defined(HAVE_NETINET_IP_H)
#ifdef HAVE_ASM_TYPES_H
# include <asm/types.h>
#endif // defined(HAVE_ASM_TYPES_H)
#ifdef HAVE_LINUX_NETLINK_H
# include <linux/netlink.h>
#endif // defined(HAVE_LINUX_NETLINK_H)
#ifdef HAVE_LINUX_RTNETLINK_H
# include <linux/rtnetlink.h>
#endif // defined(HAVE_LINUX_RTNETLINK_H)
#include "template.h"
namespace ngtcp2 {
uint8_t msghdr_get_ecn(msghdr *msg, int family) {
switch (family) {
case AF_INET:
for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IP &&
#ifdef __APPLE__
cmsg->cmsg_type == IP_RECVTOS
#else // !defined(__APPLE__)
cmsg->cmsg_type == IP_TOS
#endif // !defined(__APPLE__)
&& cmsg->cmsg_len) {
return *reinterpret_cast<uint8_t *>(CMSG_DATA(cmsg)) & IPTOS_ECN_MASK;
}
}
break;
case AF_INET6:
for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS &&
cmsg->cmsg_len) {
unsigned int tos;
memcpy(&tos, CMSG_DATA(cmsg), sizeof(int));
return tos & IPTOS_ECN_MASK;
}
}
break;
}
return 0;
}
void fd_set_recv_ecn(int fd, int family) {
unsigned int tos = 1;
switch (family) {
case AF_INET:
if (setsockopt(fd, IPPROTO_IP, IP_RECVTOS, &tos,
static_cast<socklen_t>(sizeof(tos))) == -1) {
std::cerr << "setsockopt: " << strerror(errno) << std::endl;
}
break;
case AF_INET6:
if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &tos,
static_cast<socklen_t>(sizeof(tos))) == -1) {
std::cerr << "setsockopt: " << strerror(errno) << std::endl;
}
break;
}
}
void fd_set_ip_mtu_discover(int fd, int family) {
#if defined(IP_MTU_DISCOVER) && defined(IPV6_MTU_DISCOVER)
int val;
switch (family) {
case AF_INET:
val = IP_PMTUDISC_PROBE;
if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
std::cerr << "setsockopt: IP_MTU_DISCOVER: " << strerror(errno)
<< std::endl;
}
break;
case AF_INET6:
val = IPV6_PMTUDISC_PROBE;
if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
std::cerr << "setsockopt: IPV6_MTU_DISCOVER: " << strerror(errno)
<< std::endl;
}
break;
}
#endif // defined(IP_MTU_DISCOVER) && defined(IPV6_MTU_DISCOVER)
}
void fd_set_ip_dontfrag(int fd, int family) {
#if defined(IP_DONTFRAG) && defined(IPV6_DONTFRAG)
int val = 1;
switch (family) {
case AF_INET:
if (setsockopt(fd, IPPROTO_IP, IP_DONTFRAG, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
std::cerr << "setsockopt: IP_DONTFRAG: " << strerror(errno) << std::endl;
}
break;
case AF_INET6:
if (setsockopt(fd, IPPROTO_IPV6, IPV6_DONTFRAG, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
std::cerr << "setsockopt: IPV6_DONTFRAG: " << strerror(errno)
<< std::endl;
}
break;
}
#endif // defined(IP_DONTFRAG) && defined(IPV6_DONTFRAG)
}
void fd_set_udp_gro(int fd) {
#ifdef UDP_GRO
int val = 1;
if (setsockopt(fd, IPPROTO_UDP, UDP_GRO, &val,
static_cast<socklen_t>(sizeof(val))) == -1) {
std::cerr << "setsockopt: UDP_GRO: " << strerror(errno) << std::endl;
}
#endif // defined(UDP_GRO)
}
std::optional<Address> msghdr_get_local_addr(msghdr *msg, int family) {
switch (family) {
case AF_INET:
for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
in_pktinfo pktinfo;
memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo));
Address res{
.len = sizeof(res.su.in),
.ifindex = static_cast<uint32_t>(pktinfo.ipi_ifindex),
};
auto &sa = res.su.in;
sa.sin_family = AF_INET;
sa.sin_addr = pktinfo.ipi_addr;
return res;
}
}
return {};
case AF_INET6:
for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) {
in6_pktinfo pktinfo;
memcpy(&pktinfo, CMSG_DATA(cmsg), sizeof(pktinfo));
Address res{
.len = sizeof(res.su.in6),
.ifindex = static_cast<uint32_t>(pktinfo.ipi6_ifindex),
};
auto &sa = res.su.in6;
sa.sin6_family = AF_INET6;
sa.sin6_addr = pktinfo.ipi6_addr;
return res;
}
}
return {};
}
return {};
}
size_t msghdr_get_udp_gro(msghdr *msg) {
int gso_size = 0;
#ifdef UDP_GRO
for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
if (cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) {
memcpy(&gso_size, CMSG_DATA(cmsg), sizeof(gso_size));
break;
}
}
#endif // defined(UDP_GRO)
return static_cast<size_t>(gso_size);
}
void set_port(Address &dst, const Address &src) {
switch (dst.su.storage.ss_family) {
case AF_INET:
assert(AF_INET == src.su.storage.ss_family);
dst.su.in.sin_port = src.su.in.sin_port;
return;
case AF_INET6:
assert(AF_INET6 == src.su.storage.ss_family);
dst.su.in6.sin6_port = src.su.in6.sin6_port;
return;
default:
assert(0);
}
}
#ifdef HAVE_LINUX_RTNETLINK_H
struct nlmsg {
nlmsghdr hdr;
rtmsg msg;
rtattr dst;
in_addr_union dst_addr;
};
namespace {
int send_netlink_msg(int fd, const Address &remote_addr, uint32_t seq) {
nlmsg nlmsg{
.hdr =
{
.nlmsg_type = RTM_GETROUTE,
.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
.nlmsg_seq = seq,
},
.msg =
{
.rtm_family = static_cast<unsigned char>(remote_addr.su.sa.sa_family),
.rtm_protocol = RTPROT_KERNEL,
},
.dst =
{
.rta_type = RTA_DST,
},
};
switch (remote_addr.su.sa.sa_family) {
case AF_INET:
nlmsg.dst.rta_len = RTA_LENGTH(sizeof(remote_addr.su.in.sin_addr));
memcpy(RTA_DATA(&nlmsg.dst), &remote_addr.su.in.sin_addr,
sizeof(remote_addr.su.in.sin_addr));
break;
case AF_INET6:
nlmsg.dst.rta_len = RTA_LENGTH(sizeof(remote_addr.su.in6.sin6_addr));
memcpy(RTA_DATA(&nlmsg.dst), &remote_addr.su.in6.sin6_addr,
sizeof(remote_addr.su.in6.sin6_addr));
break;
default:
assert(0);
}
nlmsg.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(nlmsg.msg) + nlmsg.dst.rta_len);
sockaddr_nl sa{
.nl_family = AF_NETLINK,
};
iovec iov{
.iov_base = &nlmsg,
.iov_len = nlmsg.hdr.nlmsg_len,
};
msghdr msg{
.msg_name = &sa,
.msg_namelen = sizeof(sa),
.msg_iov = &iov,
.msg_iovlen = 1,
};
ssize_t nwrite;
do {
nwrite = sendmsg(fd, &msg, 0);
} while (nwrite == -1 && errno == EINTR);
if (nwrite == -1) {
std::cerr << "sendmsg: Could not write netlink message: " << strerror(errno)
<< std::endl;
return -1;
}
return 0;
}
} // namespace
namespace {
int recv_netlink_msg(in_addr_union &iau, int fd, uint32_t seq) {
std::array<uint8_t, 8192> buf;
iovec iov = {
.iov_base = buf.data(),
.iov_len = buf.size(),
};
sockaddr_nl sa{};
msghdr msg{
.msg_name = &sa,
.msg_namelen = sizeof(sa),
.msg_iov = &iov,
.msg_iovlen = 1,
};
ssize_t nread;
do {
nread = recvmsg(fd, &msg, 0);
} while (nread == -1 && errno == EINTR);
if (nread == -1) {
std::cerr << "recvmsg: Could not receive netlink message: "
<< strerror(errno) << std::endl;
return -1;
}
size_t in_addrlen = 0;
for (auto hdr = reinterpret_cast<nlmsghdr *>(buf.data());
NLMSG_OK(hdr, nread); hdr = NLMSG_NEXT(hdr, nread)) {
if (seq != hdr->nlmsg_seq) {
std::cerr << "netlink: unexpected sequence number " << hdr->nlmsg_seq
<< " while expecting " << seq << std::endl;
return -1;
}
if (hdr->nlmsg_flags & NLM_F_MULTI) {
std::cerr << "netlink: unexpected NLM_F_MULTI flag set" << std::endl;
return -1;
}
switch (hdr->nlmsg_type) {
case NLMSG_DONE:
std::cerr << "netlink: unexpected NLMSG_DONE" << std::endl;
return -1;
case NLMSG_NOOP:
continue;
case NLMSG_ERROR:
std::cerr << "netlink: "
<< strerror(-static_cast<nlmsgerr *>(NLMSG_DATA(hdr))->error)
<< std::endl;
return -1;
}
auto attrlen = hdr->nlmsg_len - NLMSG_SPACE(sizeof(rtmsg));
for (auto rta = reinterpret_cast<rtattr *>(
static_cast<uint8_t *>(NLMSG_DATA(hdr)) + sizeof(rtmsg));
RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) {
if (rta->rta_type != RTA_PREFSRC) {
continue;
}
switch (static_cast<rtmsg *>(NLMSG_DATA(hdr))->rtm_family) {
case AF_INET:
in_addrlen = sizeof(in_addr);
break;
case AF_INET6:
in_addrlen = sizeof(in6_addr);
break;
default:
assert(0);
abort();
}
if (RTA_LENGTH(in_addrlen) != rta->rta_len) {
return -1;
}
memcpy(&iau, RTA_DATA(rta), in_addrlen);
break;
}
}
if (in_addrlen == 0) {
return -1;
}
// Read ACK
sa = {};
msg = {};
msg.msg_name = &sa;
msg.msg_namelen = sizeof(sa);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
int error = -1;
do {
nread = recvmsg(fd, &msg, 0);
} while (nread == -1 && errno == EINTR);
if (nread == -1) {
std::cerr << "recvmsg: Could not receive netlink message: "
<< strerror(errno) << std::endl;
return -1;
}
error = -1;
for (auto hdr = reinterpret_cast<nlmsghdr *>(buf.data());
NLMSG_OK(hdr, nread); hdr = NLMSG_NEXT(hdr, nread)) {
if (seq != hdr->nlmsg_seq) {
std::cerr << "netlink: unexpected sequence number " << hdr->nlmsg_seq
<< " while expecting " << seq << std::endl;
return -1;
}
if (hdr->nlmsg_flags & NLM_F_MULTI) {
std::cerr << "netlink: unexpected NLM_F_MULTI flag set" << std::endl;
return -1;
}
switch (hdr->nlmsg_type) {
case NLMSG_DONE:
std::cerr << "netlink: unexpected NLMSG_DONE" << std::endl;
return -1;
case NLMSG_NOOP:
continue;
case NLMSG_ERROR:
error = -static_cast<nlmsgerr *>(NLMSG_DATA(hdr))->error;
if (error == 0) {
break;
}
std::cerr << "netlink: " << strerror(error) << std::endl;
return -1;
}
}
if (error != 0) {
return -1;
}
return 0;
}
} // namespace
int get_local_addr(in_addr_union &iau, const Address &remote_addr) {
sockaddr_nl sa{
.nl_family = AF_NETLINK,
};
auto fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (fd == -1) {
std::cerr << "socket: Could not create netlink socket: " << strerror(errno)
<< std::endl;
return -1;
}
auto fd_d = defer(close, fd);
if (bind(fd, reinterpret_cast<sockaddr *>(&sa), sizeof(sa)) == -1) {
std::cerr << "bind: Could not bind netlink socket: " << strerror(errno)
<< std::endl;
return -1;
}
uint32_t seq = 1;
if (send_netlink_msg(fd, remote_addr, seq) != 0) {
return -1;
}
return recv_netlink_msg(iau, fd, seq);
}
#endif // defined(HAVE_LINUX_NETLINK_H)
bool addreq(const sockaddr *sa, const in_addr_union &iau) {
switch (sa->sa_family) {
case AF_INET:
return memcmp(&reinterpret_cast<const sockaddr_in *>(sa)->sin_addr, &iau.in,
sizeof(iau.in)) == 0;
case AF_INET6:
return memcmp(&reinterpret_cast<const sockaddr_in6 *>(sa)->sin6_addr,
&iau.in6, sizeof(iau.in6)) == 0;
default:
assert(0);
abort();
}
}
} // namespace ngtcp2

99
deps/ngtcp2/ngtcp2/examples/shared.h vendored Normal file
View File

@ -0,0 +1,99 @@
/*
* ngtcp2
*
* Copyright (c) 2017 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef SHARED_H
#define SHARED_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <optional>
#include <string_view>
#include <span>
#include <ngtcp2/ngtcp2.h>
#include "network.h"
using namespace std::literals;
namespace ngtcp2 {
enum class AppProtocol {
H3,
HQ,
};
template <size_t N>
consteval std::span<const uint8_t> as_uint8_span(const uint8_t (&s)[N]) {
return {s, N - 1};
}
constexpr uint8_t RAW_HQ_ALPN[] = "\xahq-interop";
constexpr auto HQ_ALPN = as_uint8_span(RAW_HQ_ALPN);
constexpr auto HQ_ALPN_V1 = as_uint8_span(RAW_HQ_ALPN);
constexpr uint8_t RAW_H3_ALPN[] = "\x2h3";
constexpr auto H3_ALPN = as_uint8_span(RAW_H3_ALPN);
constexpr auto H3_ALPN_V1 = as_uint8_span(RAW_H3_ALPN);
constexpr uint32_t TLS_ALERT_ECH_REQUIRED = 121;
// msghdr_get_ecn gets ECN bits from |msg|. |family| is the address
// family from which packet is received.
uint8_t msghdr_get_ecn(msghdr *msg, int family);
// fd_set_recv_ecn sets socket option to |fd| so that it can receive
// ECN bits.
void fd_set_recv_ecn(int fd, int family);
// fd_set_ip_mtu_discover sets IP(V6)_MTU_DISCOVER socket option to
// |fd|.
void fd_set_ip_mtu_discover(int fd, int family);
// fd_set_ip_dontfrag sets IP(V6)_DONTFRAG socket option to |fd|.
void fd_set_ip_dontfrag(int fd, int family);
// fd_set_udp_gro sets UDP_GRO socket option to |fd|.
void fd_set_udp_gro(int fd);
std::optional<Address> msghdr_get_local_addr(msghdr *msg, int family);
// msghdr_get_udp_gro returns UDP_GRO value from |msg|. If UDP_GRO is
// not found, or UDP_GRO is not supported, this function returns 0.
size_t msghdr_get_udp_gro(msghdr *msg);
void set_port(Address &dst, const Address &src);
// get_local_addr stores preferred local address (interface address)
// in |iau| for a given destination address |remote_addr|.
int get_local_addr(in_addr_union &iau, const Address &remote_addr);
// addreq returns true if |sa| and |iau| contain the same address.
bool addreq(const sockaddr *sa, const in_addr_union &iau);
} // namespace ngtcp2
#endif // !defined(SHARED_H)

View File

@ -0,0 +1,662 @@
/*
* ngtcp2
*
* Copyright (c) 2021 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* defined(HAVE_CONFIG_H) */
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <ngtcp2/ngtcp2_crypto_quictls.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <ev.h>
#define REMOTE_HOST "127.0.0.1"
#define REMOTE_PORT "4433"
#define ALPN "\xahq-interop"
#define MESSAGE "GET /\r\n"
/*
* Example 1: Handshake with www.google.com
*
* #define REMOTE_HOST "www.google.com"
* #define REMOTE_PORT "443"
* #define ALPN "\x2h3"
*
* and undefine MESSAGE macro.
*/
static uint64_t timestamp(void) {
struct timespec tp;
if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) {
fprintf(stderr, "clock_gettime: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
return (uint64_t)tp.tv_sec * NGTCP2_SECONDS + (uint64_t)tp.tv_nsec;
}
static int create_sock(struct sockaddr *addr, socklen_t *paddrlen,
const char *host, const char *port) {
struct addrinfo hints = {0};
struct addrinfo *res, *rp;
int rv;
int fd = -1;
hints.ai_flags = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
rv = getaddrinfo(host, port, &hints, &res);
if (rv != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return -1;
}
for (rp = res; rp; rp = rp->ai_next) {
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (fd == -1) {
continue;
}
break;
}
if (fd == -1) {
goto end;
}
*paddrlen = rp->ai_addrlen;
memcpy(addr, rp->ai_addr, rp->ai_addrlen);
end:
freeaddrinfo(res);
return fd;
}
static int connect_sock(struct sockaddr *local_addr, socklen_t *plocal_addrlen,
int fd, const struct sockaddr *remote_addr,
size_t remote_addrlen) {
socklen_t len;
if (connect(fd, remote_addr, (socklen_t)remote_addrlen) != 0) {
fprintf(stderr, "connect: %s\n", strerror(errno));
return -1;
}
len = *plocal_addrlen;
if (getsockname(fd, local_addr, &len) == -1) {
fprintf(stderr, "getsockname: %s\n", strerror(errno));
return -1;
}
*plocal_addrlen = len;
return 0;
}
struct client {
ngtcp2_crypto_conn_ref conn_ref;
int fd;
struct sockaddr_storage local_addr;
socklen_t local_addrlen;
SSL_CTX *ssl_ctx;
SSL *ssl;
ngtcp2_conn *conn;
struct {
int64_t stream_id;
const uint8_t *data;
size_t datalen;
size_t nwrite;
} stream;
ngtcp2_ccerr last_error;
ev_io rev;
ev_timer timer;
};
static int numeric_host_family(const char *hostname, int family) {
uint8_t dst[sizeof(struct in6_addr)];
return inet_pton(family, hostname, dst) == 1;
}
static int numeric_host(const char *hostname) {
return numeric_host_family(hostname, AF_INET) ||
numeric_host_family(hostname, AF_INET6);
}
static int client_ssl_init(struct client *c) {
c->ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!c->ssl_ctx) {
fprintf(stderr, "SSL_CTX_new: %s\n",
ERR_error_string(ERR_get_error(), NULL));
return -1;
}
if (ngtcp2_crypto_quictls_configure_client_context(c->ssl_ctx) != 0) {
fprintf(stderr, "ngtcp2_crypto_quictls_configure_client_context failed\n");
return -1;
}
c->ssl = SSL_new(c->ssl_ctx);
if (!c->ssl) {
fprintf(stderr, "SSL_new: %s\n", ERR_error_string(ERR_get_error(), NULL));
return -1;
}
SSL_set_app_data(c->ssl, &c->conn_ref);
SSL_set_connect_state(c->ssl);
SSL_set_alpn_protos(c->ssl, (const unsigned char *)ALPN, sizeof(ALPN) - 1);
if (!numeric_host(REMOTE_HOST)) {
SSL_set_tlsext_host_name(c->ssl, REMOTE_HOST);
}
return 0;
}
static void rand_cb(uint8_t *dest, size_t destlen,
const ngtcp2_rand_ctx *rand_ctx) {
int rv;
(void)rand_ctx;
rv = RAND_bytes(dest, (int)destlen);
if (rv != 1) {
assert(0);
abort();
}
}
static int get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid,
uint8_t *token, size_t cidlen,
void *user_data) {
(void)conn;
(void)user_data;
if (RAND_bytes(cid->data, (int)cidlen) != 1) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
cid->datalen = cidlen;
if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
static int extend_max_local_streams_bidi(ngtcp2_conn *conn,
uint64_t max_streams,
void *user_data) {
#ifdef MESSAGE
struct client *c = user_data;
int rv;
int64_t stream_id;
(void)max_streams;
if (c->stream.stream_id != -1) {
return 0;
}
rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL);
if (rv != 0) {
return 0;
}
c->stream.stream_id = stream_id;
c->stream.data = (const uint8_t *)MESSAGE;
c->stream.datalen = sizeof(MESSAGE) - 1;
return 0;
#else /* !defined(MESSAGE) */
(void)conn;
(void)max_streams;
(void)user_data;
return 0;
#endif /* !defined(MESSAGE) */
}
static void log_printf(void *user_data, const char *fmt, ...) {
va_list ap;
(void)user_data;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
}
static int client_quic_init(struct client *c,
const struct sockaddr *remote_addr,
socklen_t remote_addrlen,
const struct sockaddr *local_addr,
socklen_t local_addrlen) {
ngtcp2_path path = {
.local =
{
.addr = (struct sockaddr *)local_addr,
.addrlen = local_addrlen,
},
.remote =
{
.addr = (struct sockaddr *)remote_addr,
.addrlen = remote_addrlen,
},
};
ngtcp2_callbacks callbacks = {
.client_initial = ngtcp2_crypto_client_initial_cb,
.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb,
.encrypt = ngtcp2_crypto_encrypt_cb,
.decrypt = ngtcp2_crypto_decrypt_cb,
.hp_mask = ngtcp2_crypto_hp_mask_cb,
.recv_retry = ngtcp2_crypto_recv_retry_cb,
.extend_max_local_streams_bidi = extend_max_local_streams_bidi,
.rand = rand_cb,
.get_new_connection_id = get_new_connection_id_cb,
.update_key = ngtcp2_crypto_update_key_cb,
.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb,
.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb,
.version_negotiation = ngtcp2_crypto_version_negotiation_cb,
};
ngtcp2_cid dcid, scid;
ngtcp2_settings settings;
ngtcp2_transport_params params;
int rv;
dcid.datalen = NGTCP2_MIN_INITIAL_DCIDLEN;
if (RAND_bytes(dcid.data, (int)dcid.datalen) != 1) {
fprintf(stderr, "RAND_bytes failed\n");
return -1;
}
scid.datalen = 8;
if (RAND_bytes(scid.data, (int)scid.datalen) != 1) {
fprintf(stderr, "RAND_bytes failed\n");
return -1;
}
ngtcp2_settings_default(&settings);
settings.initial_ts = timestamp();
settings.log_printf = log_printf;
ngtcp2_transport_params_default(&params);
params.initial_max_streams_uni = 3;
params.initial_max_stream_data_bidi_local = 128 * 1024;
params.initial_max_data = 1024 * 1024;
rv =
ngtcp2_conn_client_new(&c->conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1,
&callbacks, &settings, &params, NULL, c);
if (rv != 0) {
fprintf(stderr, "ngtcp2_conn_client_new: %s\n", ngtcp2_strerror(rv));
return -1;
}
ngtcp2_conn_set_tls_native_handle(c->conn, c->ssl);
return 0;
}
static int client_read(struct client *c) {
uint8_t buf[65536];
struct sockaddr_storage addr;
struct iovec iov = {
.iov_base = buf,
.iov_len = sizeof(buf),
};
struct msghdr msg = {0};
ssize_t nread;
ngtcp2_path path;
ngtcp2_pkt_info pi = {0};
int rv;
msg.msg_name = &addr;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
for (;;) {
msg.msg_namelen = sizeof(addr);
nread = recvmsg(c->fd, &msg, MSG_DONTWAIT);
if (nread == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
fprintf(stderr, "recvmsg: %s\n", strerror(errno));
}
break;
}
path.local.addrlen = c->local_addrlen;
path.local.addr = (struct sockaddr *)&c->local_addr;
path.remote.addrlen = msg.msg_namelen;
path.remote.addr = msg.msg_name;
rv = ngtcp2_conn_read_pkt(c->conn, &path, &pi, buf, (size_t)nread,
timestamp());
if (rv != 0) {
fprintf(stderr, "ngtcp2_conn_read_pkt: %s\n", ngtcp2_strerror(rv));
if (!c->last_error.error_code) {
if (rv == NGTCP2_ERR_CRYPTO) {
ngtcp2_ccerr_set_tls_alert(
&c->last_error, ngtcp2_conn_get_tls_alert(c->conn), NULL, 0);
} else {
ngtcp2_ccerr_set_liberr(&c->last_error, rv, NULL, 0);
}
}
return -1;
}
}
return 0;
}
static int client_send_packet(struct client *c, const uint8_t *data,
size_t datalen) {
struct iovec iov = {
.iov_base = (uint8_t *)data,
.iov_len = datalen,
};
struct msghdr msg = {0};
ssize_t nwrite;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
do {
nwrite = sendmsg(c->fd, &msg, 0);
} while (nwrite == -1 && errno == EINTR);
if (nwrite == -1) {
fprintf(stderr, "sendmsg: %s\n", strerror(errno));
return -1;
}
return 0;
}
static size_t client_get_message(struct client *c, int64_t *pstream_id,
int *pfin, ngtcp2_vec *datav,
size_t datavcnt) {
if (datavcnt == 0) {
return 0;
}
if (c->stream.stream_id != -1 && c->stream.nwrite < c->stream.datalen) {
*pstream_id = c->stream.stream_id;
*pfin = 1;
datav->base = (uint8_t *)c->stream.data + c->stream.nwrite;
datav->len = c->stream.datalen - c->stream.nwrite;
return 1;
}
*pstream_id = -1;
*pfin = 0;
datav->base = NULL;
datav->len = 0;
return 0;
}
static int client_write_streams(struct client *c) {
ngtcp2_tstamp ts = timestamp();
ngtcp2_pkt_info pi;
ngtcp2_ssize nwrite;
uint8_t buf[1452];
ngtcp2_path_storage ps;
ngtcp2_vec datav;
size_t datavcnt;
int64_t stream_id;
ngtcp2_ssize wdatalen;
uint32_t flags;
int fin;
ngtcp2_path_storage_zero(&ps);
for (;;) {
datavcnt = client_get_message(c, &stream_id, &fin, &datav, 1);
flags = NGTCP2_WRITE_STREAM_FLAG_MORE;
if (fin) {
flags |= NGTCP2_WRITE_STREAM_FLAG_FIN;
}
nwrite = ngtcp2_conn_writev_stream(c->conn, &ps.path, &pi, buf, sizeof(buf),
&wdatalen, flags, stream_id, &datav,
datavcnt, ts);
if (nwrite < 0) {
switch (nwrite) {
case NGTCP2_ERR_WRITE_MORE:
c->stream.nwrite += (size_t)wdatalen;
continue;
default:
fprintf(stderr, "ngtcp2_conn_writev_stream: %s\n",
ngtcp2_strerror((int)nwrite));
ngtcp2_ccerr_set_liberr(&c->last_error, (int)nwrite, NULL, 0);
return -1;
}
}
if (nwrite == 0) {
return 0;
}
if (wdatalen > 0) {
c->stream.nwrite += (size_t)wdatalen;
}
if (client_send_packet(c, buf, (size_t)nwrite) != 0) {
break;
}
}
return 0;
}
static int client_write(struct client *c) {
ngtcp2_tstamp expiry, now;
ev_tstamp t;
if (client_write_streams(c) != 0) {
return -1;
}
expiry = ngtcp2_conn_get_expiry(c->conn);
now = timestamp();
t = expiry < now ? 1e-9 : (ev_tstamp)(expiry - now) / NGTCP2_SECONDS;
c->timer.repeat = t;
ev_timer_again(EV_DEFAULT, &c->timer);
return 0;
}
static int client_handle_expiry(struct client *c) {
int rv = ngtcp2_conn_handle_expiry(c->conn, timestamp());
if (rv != 0) {
fprintf(stderr, "ngtcp2_conn_handle_expiry: %s\n", ngtcp2_strerror(rv));
return -1;
}
return 0;
}
static void client_close(struct client *c) {
ngtcp2_ssize nwrite;
ngtcp2_pkt_info pi;
ngtcp2_path_storage ps;
uint8_t buf[1280];
if (ngtcp2_conn_in_closing_period(c->conn) ||
ngtcp2_conn_in_draining_period(c->conn)) {
goto fin;
}
ngtcp2_path_storage_zero(&ps);
nwrite = ngtcp2_conn_write_connection_close(
c->conn, &ps.path, &pi, buf, sizeof(buf), &c->last_error, timestamp());
if (nwrite < 0) {
fprintf(stderr, "ngtcp2_conn_write_connection_close: %s\n",
ngtcp2_strerror((int)nwrite));
goto fin;
}
client_send_packet(c, buf, (size_t)nwrite);
fin:
ev_break(EV_DEFAULT, EVBREAK_ALL);
}
static void read_cb(struct ev_loop *loop, ev_io *w, int revents) {
struct client *c = w->data;
(void)loop;
(void)revents;
if (client_read(c) != 0) {
client_close(c);
return;
}
if (client_write(c) != 0) {
client_close(c);
}
}
static void timer_cb(struct ev_loop *loop, ev_timer *w, int revents) {
struct client *c = w->data;
(void)loop;
(void)revents;
if (client_handle_expiry(c) != 0) {
client_close(c);
return;
}
if (client_write(c) != 0) {
client_close(c);
}
}
static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) {
struct client *c = conn_ref->user_data;
return c->conn;
}
static int client_init(struct client *c) {
struct sockaddr_storage remote_addr, local_addr;
socklen_t remote_addrlen, local_addrlen = sizeof(local_addr);
memset(c, 0, sizeof(*c));
ngtcp2_ccerr_default(&c->last_error);
c->fd = create_sock((struct sockaddr *)&remote_addr, &remote_addrlen,
REMOTE_HOST, REMOTE_PORT);
if (c->fd == -1) {
return -1;
}
if (connect_sock((struct sockaddr *)&local_addr, &local_addrlen, c->fd,
(struct sockaddr *)&remote_addr, remote_addrlen) != 0) {
return -1;
}
memcpy(&c->local_addr, &local_addr, sizeof(c->local_addr));
c->local_addrlen = local_addrlen;
if (client_ssl_init(c) != 0) {
return -1;
}
if (client_quic_init(c, (struct sockaddr *)&remote_addr, remote_addrlen,
(struct sockaddr *)&local_addr, local_addrlen) != 0) {
return -1;
}
c->stream.stream_id = -1;
c->conn_ref.get_conn = get_conn;
c->conn_ref.user_data = c;
ev_io_init(&c->rev, read_cb, c->fd, EV_READ);
c->rev.data = c;
ev_io_start(EV_DEFAULT, &c->rev);
ev_timer_init(&c->timer, timer_cb, 0., 0.);
c->timer.data = c;
return 0;
}
static void client_free(struct client *c) {
ngtcp2_conn_del(c->conn);
SSL_free(c->ssl);
SSL_CTX_free(c->ssl_ctx);
}
int main(void) {
struct client c;
srandom((unsigned int)timestamp());
if (client_init(&c) != 0) {
exit(EXIT_FAILURE);
}
if (client_write(&c) != 0) {
exit(EXIT_FAILURE);
}
ev_run(EV_DEFAULT, 0);
client_free(&c);
return 0;
}

115
deps/ngtcp2/ngtcp2/examples/siphash.cc vendored Normal file
View File

@ -0,0 +1,115 @@
/* Copyright 2019 The BoringSSL Authors
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
/*
* ngtcp2
*
* Copyright (c) 2025 nghttp2 contributors
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <cstdint>
#include <cstring>
#include "siphash.h"
namespace {
auto CRYPTO_load_u64_le(std::span<const uint8_t, sizeof(uint64_t)> in) {
uint64_t v;
memcpy(&v, in.data(), sizeof(v));
if constexpr (std::endian::native == std::endian::big) {
return byteswap(v);
}
return v;
}
} // namespace
namespace {
constexpr void siphash_round(uint64_t v[4]) {
v[0] += v[1];
v[2] += v[3];
v[1] = std::rotl(v[1], 13);
v[3] = std::rotl(v[3], 16);
v[1] ^= v[0];
v[3] ^= v[2];
v[0] = std::rotl(v[0], 32);
v[2] += v[1];
v[0] += v[3];
v[1] = std::rotl(v[1], 17);
v[3] = std::rotl(v[3], 21);
v[1] ^= v[2];
v[3] ^= v[0];
v[2] = std::rotl(v[2], 32);
}
} // namespace
uint64_t siphash24(std::span<const uint64_t, 2> key,
std::span<const uint8_t> input) {
const auto orig_input_len = input.size();
uint64_t v[]{
key[0] ^ UINT64_C(0x736f6d6570736575),
key[1] ^ UINT64_C(0x646f72616e646f6d),
key[0] ^ UINT64_C(0x6c7967656e657261),
key[1] ^ UINT64_C(0x7465646279746573),
};
while (input.size() >= sizeof(uint64_t)) {
auto m = CRYPTO_load_u64_le(input.first<sizeof(uint64_t)>());
v[3] ^= m;
siphash_round(v);
siphash_round(v);
v[0] ^= m;
input = input.subspan(sizeof(uint64_t));
}
std::array<uint8_t, sizeof(uint64_t)> last_block{};
std::ranges::copy(input, std::ranges::begin(last_block));
last_block.back() = orig_input_len & 0xff;
auto last_block_word = CRYPTO_load_u64_le(last_block);
v[3] ^= last_block_word;
siphash_round(v);
siphash_round(v);
v[0] ^= last_block_word;
v[2] ^= 0xff;
siphash_round(v);
siphash_round(v);
siphash_round(v);
siphash_round(v);
return v[0] ^ v[1] ^ v[2] ^ v[3];
}

62
deps/ngtcp2/ngtcp2/examples/siphash.h vendored Normal file
View File

@ -0,0 +1,62 @@
/* Copyright 2019 The BoringSSL Authors
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
/*
* ngtcp2
*
* Copyright (c) 2025 nghttp2 contributors
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef SIPHASH_H
#define SIPHASH_H
#include <bit>
#include <concepts>
#include <algorithm>
#include <span>
// SipHash is a fast, secure PRF that is often used for hash tables.
// siphash24 implements SipHash-2-4. See
// https://131002.net/siphash/siphash.pdf
uint64_t siphash24(std::span<const uint64_t, 2> key,
std::span<const uint8_t> input);
// Define here to be usable in tests.
template <std::integral T> T byteswap(T v) {
auto c = std::bit_cast<std::array<uint8_t, sizeof(T)>>(v);
std::ranges::reverse(c);
return std::bit_cast<T>(c);
}
#endif // !defined(SIPHASH_H)

View File

@ -0,0 +1,98 @@
/*
* ngtcp2
*
* Copyright (c) 2025 nghttp2 contributors
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "siphash_test.h"
#include <cstring>
#include <array>
#include <numeric>
#include "siphash.h"
#include "siphash_vector.h"
namespace ngtcp2 {
namespace {
const MunitTest tests[]{
munit_void_test(test_siphash),
munit_void_test(test_siphash_vector),
munit_test_end(),
};
} // namespace
const MunitSuite siphash_suite{
.prefix = "/siphash",
.tests = tests,
};
void test_siphash(void) {
std::array<uint8_t, 16> key_bytes;
std::iota(std::ranges::begin(key_bytes), std::ranges::end(key_bytes), 0);
std::array<uint64_t, 2> key;
memcpy(key.data(), key_bytes.data(), key_bytes.size());
if constexpr (std::endian::native == std::endian::big) {
key[0] = byteswap(key[0]);
key[1] = byteswap(key[1]);
}
std::array<uint8_t, 15> input;
std::iota(std::ranges::begin(input), std::ranges::end(input), 0);
assert_uint64(0xa129ca6149be45e5ull, ==, siphash24(key, input));
}
void test_siphash_vector(void) {
std::array<uint8_t, 16> key_bytes;
std::iota(std::ranges::begin(key_bytes), std::ranges::end(key_bytes), 0);
std::array<uint64_t, 2> key;
memcpy(key.data(), key_bytes.data(), key_bytes.size());
if constexpr (std::endian::native == std::endian::big) {
key[0] = byteswap(key[0]);
key[1] = byteswap(key[1]);
}
std::array<uint8_t, 64> in;
for (size_t i = 0; i < 64; ++i) {
in[i] = static_cast<uint8_t>(i);
auto h = siphash24(key, std::span{in}.first(i));
uint64_t expect;
memcpy(&expect, &vectors_sip64[i], sizeof(expect));
if constexpr (std::endian::native == std::endian::big) {
expect = byteswap(expect);
}
assert_uint64(expect, ==, h);
}
}
} // namespace ngtcp2

View File

@ -0,0 +1,46 @@
/*
* ngtcp2
*
* Copyright (c) 2025 nghttp2 contributors
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef SIPHASH_TEST_H
#define SIPHASH_TEST_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#define MUNIT_ENABLE_ASSERT_ALIASES
#include "munit.h"
namespace ngtcp2 {
extern const MunitSuite siphash_suite;
munit_void_test_decl(test_siphash)
munit_void_test_decl(test_siphash_vector)
} // namespace ngtcp2
#endif // !defined(SIPHASH_TEST_H)

View File

@ -0,0 +1,645 @@
// https://github.com/veorq/SipHash/blob/f26d35e964c6290ffe23d9043475ad3129f409e0/vectors.h
#include <stdint.h>
const uint8_t vectors_sip64[64][8] = {
{
0x31,
0x0e,
0x0e,
0xdd,
0x47,
0xdb,
0x6f,
0x72,
},
{
0xfd,
0x67,
0xdc,
0x93,
0xc5,
0x39,
0xf8,
0x74,
},
{
0x5a,
0x4f,
0xa9,
0xd9,
0x09,
0x80,
0x6c,
0x0d,
},
{
0x2d,
0x7e,
0xfb,
0xd7,
0x96,
0x66,
0x67,
0x85,
},
{
0xb7,
0x87,
0x71,
0x27,
0xe0,
0x94,
0x27,
0xcf,
},
{
0x8d,
0xa6,
0x99,
0xcd,
0x64,
0x55,
0x76,
0x18,
},
{
0xce,
0xe3,
0xfe,
0x58,
0x6e,
0x46,
0xc9,
0xcb,
},
{
0x37,
0xd1,
0x01,
0x8b,
0xf5,
0x00,
0x02,
0xab,
},
{
0x62,
0x24,
0x93,
0x9a,
0x79,
0xf5,
0xf5,
0x93,
},
{
0xb0,
0xe4,
0xa9,
0x0b,
0xdf,
0x82,
0x00,
0x9e,
},
{
0xf3,
0xb9,
0xdd,
0x94,
0xc5,
0xbb,
0x5d,
0x7a,
},
{
0xa7,
0xad,
0x6b,
0x22,
0x46,
0x2f,
0xb3,
0xf4,
},
{
0xfb,
0xe5,
0x0e,
0x86,
0xbc,
0x8f,
0x1e,
0x75,
},
{
0x90,
0x3d,
0x84,
0xc0,
0x27,
0x56,
0xea,
0x14,
},
{
0xee,
0xf2,
0x7a,
0x8e,
0x90,
0xca,
0x23,
0xf7,
},
{
0xe5,
0x45,
0xbe,
0x49,
0x61,
0xca,
0x29,
0xa1,
},
{
0xdb,
0x9b,
0xc2,
0x57,
0x7f,
0xcc,
0x2a,
0x3f,
},
{
0x94,
0x47,
0xbe,
0x2c,
0xf5,
0xe9,
0x9a,
0x69,
},
{
0x9c,
0xd3,
0x8d,
0x96,
0xf0,
0xb3,
0xc1,
0x4b,
},
{
0xbd,
0x61,
0x79,
0xa7,
0x1d,
0xc9,
0x6d,
0xbb,
},
{
0x98,
0xee,
0xa2,
0x1a,
0xf2,
0x5c,
0xd6,
0xbe,
},
{
0xc7,
0x67,
0x3b,
0x2e,
0xb0,
0xcb,
0xf2,
0xd0,
},
{
0x88,
0x3e,
0xa3,
0xe3,
0x95,
0x67,
0x53,
0x93,
},
{
0xc8,
0xce,
0x5c,
0xcd,
0x8c,
0x03,
0x0c,
0xa8,
},
{
0x94,
0xaf,
0x49,
0xf6,
0xc6,
0x50,
0xad,
0xb8,
},
{
0xea,
0xb8,
0x85,
0x8a,
0xde,
0x92,
0xe1,
0xbc,
},
{
0xf3,
0x15,
0xbb,
0x5b,
0xb8,
0x35,
0xd8,
0x17,
},
{
0xad,
0xcf,
0x6b,
0x07,
0x63,
0x61,
0x2e,
0x2f,
},
{
0xa5,
0xc9,
0x1d,
0xa7,
0xac,
0xaa,
0x4d,
0xde,
},
{
0x71,
0x65,
0x95,
0x87,
0x66,
0x50,
0xa2,
0xa6,
},
{
0x28,
0xef,
0x49,
0x5c,
0x53,
0xa3,
0x87,
0xad,
},
{
0x42,
0xc3,
0x41,
0xd8,
0xfa,
0x92,
0xd8,
0x32,
},
{
0xce,
0x7c,
0xf2,
0x72,
0x2f,
0x51,
0x27,
0x71,
},
{
0xe3,
0x78,
0x59,
0xf9,
0x46,
0x23,
0xf3,
0xa7,
},
{
0x38,
0x12,
0x05,
0xbb,
0x1a,
0xb0,
0xe0,
0x12,
},
{
0xae,
0x97,
0xa1,
0x0f,
0xd4,
0x34,
0xe0,
0x15,
},
{
0xb4,
0xa3,
0x15,
0x08,
0xbe,
0xff,
0x4d,
0x31,
},
{
0x81,
0x39,
0x62,
0x29,
0xf0,
0x90,
0x79,
0x02,
},
{
0x4d,
0x0c,
0xf4,
0x9e,
0xe5,
0xd4,
0xdc,
0xca,
},
{
0x5c,
0x73,
0x33,
0x6a,
0x76,
0xd8,
0xbf,
0x9a,
},
{
0xd0,
0xa7,
0x04,
0x53,
0x6b,
0xa9,
0x3e,
0x0e,
},
{
0x92,
0x59,
0x58,
0xfc,
0xd6,
0x42,
0x0c,
0xad,
},
{
0xa9,
0x15,
0xc2,
0x9b,
0xc8,
0x06,
0x73,
0x18,
},
{
0x95,
0x2b,
0x79,
0xf3,
0xbc,
0x0a,
0xa6,
0xd4,
},
{
0xf2,
0x1d,
0xf2,
0xe4,
0x1d,
0x45,
0x35,
0xf9,
},
{
0x87,
0x57,
0x75,
0x19,
0x04,
0x8f,
0x53,
0xa9,
},
{
0x10,
0xa5,
0x6c,
0xf5,
0xdf,
0xcd,
0x9a,
0xdb,
},
{
0xeb,
0x75,
0x09,
0x5c,
0xcd,
0x98,
0x6c,
0xd0,
},
{
0x51,
0xa9,
0xcb,
0x9e,
0xcb,
0xa3,
0x12,
0xe6,
},
{
0x96,
0xaf,
0xad,
0xfc,
0x2c,
0xe6,
0x66,
0xc7,
},
{
0x72,
0xfe,
0x52,
0x97,
0x5a,
0x43,
0x64,
0xee,
},
{
0x5a,
0x16,
0x45,
0xb2,
0x76,
0xd5,
0x92,
0xa1,
},
{
0xb2,
0x74,
0xcb,
0x8e,
0xbf,
0x87,
0x87,
0x0a,
},
{
0x6f,
0x9b,
0xb4,
0x20,
0x3d,
0xe7,
0xb3,
0x81,
},
{
0xea,
0xec,
0xb2,
0xa3,
0x0b,
0x22,
0xa8,
0x7f,
},
{
0x99,
0x24,
0xa4,
0x3c,
0xc1,
0x31,
0x57,
0x24,
},
{
0xbd,
0x83,
0x8d,
0x3a,
0xaf,
0xbf,
0x8d,
0xb7,
},
{
0x0b,
0x1a,
0x2a,
0x32,
0x65,
0xd5,
0x1a,
0xea,
},
{
0x13,
0x50,
0x79,
0xa3,
0x23,
0x1c,
0xe6,
0x60,
},
{
0x93,
0x2b,
0x28,
0x46,
0xe4,
0xd7,
0x06,
0x66,
},
{
0xe1,
0x91,
0x5f,
0x5c,
0xb1,
0xec,
0xa4,
0x6c,
},
{
0xf3,
0x25,
0x96,
0x5c,
0xa1,
0x6d,
0x62,
0x9f,
},
{
0x57,
0x5f,
0xf2,
0x8e,
0x60,
0x38,
0x1b,
0xe5,
},
{
0x72,
0x45,
0x06,
0xeb,
0x4c,
0x32,
0x8a,
0x95,
},
};

91
deps/ngtcp2/ngtcp2/examples/template.h vendored Normal file
View File

@ -0,0 +1,91 @@
/*
* ngtcp2
*
* Copyright (c) 2017 ngtcp2 contributors
* Copyright (c) 2015 ngttp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TEMPLATE_H
#define TEMPLATE_H
#include <functional>
#include <utility>
#include <type_traits>
#include <span>
template <std::integral T>
[[nodiscard]] constexpr auto as_unsigned(T n) noexcept {
return static_cast<std::make_unsigned_t<T>>(n);
}
template <std::unsigned_integral T>
[[nodiscard]] constexpr auto as_signed(T n) noexcept {
return static_cast<std::make_signed_t<T>>(n);
}
// inspired by <http://blog.korfuri.fr/post/go-defer-in-cpp/>, but our
// template can take functions returning other than void.
template <typename F, typename... T> struct Defer {
Defer(F &&f, T &&...t)
: f(std::bind(std::forward<F>(f), std::forward<T>(t)...)) {}
Defer(Defer &&o) noexcept : f(std::move(o.f)) {}
~Defer() { f(); }
using ResultType = std::invoke_result_t<F, T...>;
std::function<ResultType()> f;
};
template <typename F, typename... T> Defer<F, T...> defer(F &&f, T &&...t) {
return Defer<F, T...>(std::forward<F>(f), std::forward<T>(t)...);
}
template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) {
return N;
}
template <typename T, size_t N> constexpr size_t str_size(T (&)[N]) {
return N - 1;
}
// User-defined literals for K, M, and G (powers of 1024)
constexpr unsigned long long operator""_k(unsigned long long k) {
return k * 1024;
}
constexpr unsigned long long operator""_m(unsigned long long m) {
return m * 1024 * 1024;
}
constexpr unsigned long long operator""_g(unsigned long long g) {
return g * 1024 * 1024 * 1024;
}
template <typename T, std::size_t N>
[[nodiscard]] std::span<uint8_t, N == std::dynamic_extent ? std::dynamic_extent
: N * sizeof(T)>
as_writable_uint8_span(std::span<T, N> s) noexcept {
return std::span<uint8_t, N == std::dynamic_extent ? std::dynamic_extent
: N * sizeof(T)>{
reinterpret_cast<uint8_t *>(s.data()), s.size_bytes()};
}
#endif // !defined(TEMPLATE_H)

View File

@ -0,0 +1,60 @@
Examples Tests
==============
This is a ``pytest`` suite intended to verify interoperability between
the different example clients and servers built.
You run it by executing ``pytest`` on top level project dir or in
the examples/tests directory.
.. code-block:: text
examples/test> pytest
ngtcp2-examples: [0.9.0-DEV, crypto_libs=['quictls', 'wolfssl']]
...
Requirements
------------
You need a Python3 (3.8 is probably sufficient), ``pytest`` and the
Python ``cryptography`` module installed.
Usage
-----
If you run ``pytest`` without arguments, it will print the test suite
and a ``.`` for every test case passed. Add ``-v`` and all test cases
will be listed in the full name. Adding several ``v`` will increase the
logging level on failure output.
The name of test cases include the crypto libs of the server and client
used. For example:
.. code-block:: text
test_01_handshake.py::TestHandshake::test_01_01_get[quictls-quictls] PASSED [ 16%]
test_01_handshake.py::TestHandshake::test_01_01_get[quictls-wolfssl] PASSED
Here, ``test_01_01`` is run first with the quictls server and client and then
with the quictls server and wolfSSL client. By default, the test suite runs
all combinations of servers and clients that have been configured in the project.
To track down problems, you can restrict the test cases that are run by
matching patterns:
.. code-block:: text
# only tests with wolfSSL example server
> pytest -v -k 'wolfssl-'
# only tests with wolfSSL example client
> pytest -v -k 'test and -wolfssl'
# tests with a specific combination
> pytest -v -k 'quictls-wolfssl'
Analysing
---------
To make analysis of a broken test case easier, you best run only that
test case. Use ``pytest -vv`` (or more) to get more verbose logging.
Inspect server and client log files in ``examples/tests/gen``.

View File

View File

@ -0,0 +1,28 @@
import logging
import pytest
from .ngtcp2test import Env
@pytest.mark.usefixtures("env")
def pytest_report_header(config):
env = Env()
return [
f"ngtcp2-examples: [{env.version}, crypto_libs={env.crypto_libs}]",
f"example clients: {env.clients}",
f"example servers: {env.servers}",
]
@pytest.fixture(scope="package")
def env(pytestconfig) -> Env:
console = logging.StreamHandler()
console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
logging.getLogger('').addHandler(console)
env = Env(pytestconfig=pytestconfig)
level = logging.DEBUG if env.verbose > 0 else logging.INFO
console.setLevel(level)
logging.getLogger('').setLevel(level=level)
env.setup()
return env

View File

@ -0,0 +1,6 @@
from .env import Env, CryptoLib
from .log import LogFile
from .client import ExampleClient, ClientRun
from .server import ExampleServer, ServerRun
from .certs import Ngtcp2TestCA, Credentials
from .tls import HandShake, HSRecord

View File

@ -0,0 +1,476 @@
import os
import re
from datetime import timedelta, datetime
from typing import List, Any, Optional
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec, rsa
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption, load_pem_private_key
from cryptography.x509 import ExtendedKeyUsageOID, NameOID
EC_SUPPORTED = {}
EC_SUPPORTED.update([(curve.name.upper(), curve) for curve in [
ec.SECP192R1,
ec.SECP224R1,
ec.SECP256R1,
ec.SECP384R1,
]])
def _private_key(key_type):
if isinstance(key_type, str):
key_type = key_type.upper()
m = re.match(r'^(RSA)?(\d+)$', key_type)
if m:
key_type = int(m.group(2))
if isinstance(key_type, int):
return rsa.generate_private_key(
public_exponent=65537,
key_size=key_type,
backend=default_backend()
)
if not isinstance(key_type, ec.EllipticCurve) and key_type in EC_SUPPORTED:
key_type = EC_SUPPORTED[key_type]
return ec.generate_private_key(
curve=key_type,
backend=default_backend()
)
class CertificateSpec:
def __init__(self, name: str = None, domains: List[str] = None,
email: str = None,
key_type: str = None, single_file: bool = False,
valid_from: timedelta = timedelta(days=-1),
valid_to: timedelta = timedelta(days=89),
client: bool = False,
sub_specs: List['CertificateSpec'] = None):
self._name = name
self.domains = domains
self.client = client
self.email = email
self.key_type = key_type
self.single_file = single_file
self.valid_from = valid_from
self.valid_to = valid_to
self.sub_specs = sub_specs
@property
def name(self) -> Optional[str]:
if self._name:
return self._name
elif self.domains:
return self.domains[0]
return None
@property
def type(self) -> Optional[str]:
if self.domains and len(self.domains):
return "server"
elif self.client:
return "client"
elif self.name:
return "ca"
return None
class Credentials:
def __init__(self, name: str, cert: Any, pkey: Any, issuer: 'Credentials' = None):
self._name = name
self._cert = cert
self._pkey = pkey
self._issuer = issuer
self._cert_file = None
self._pkey_file = None
self._store = None
@property
def name(self) -> str:
return self._name
@property
def subject(self) -> x509.Name:
return self._cert.subject
@property
def key_type(self):
if isinstance(self._pkey, RSAPrivateKey):
return f"rsa{self._pkey.key_size}"
elif isinstance(self._pkey, EllipticCurvePrivateKey):
return f"{self._pkey.curve.name}"
else:
raise Exception(f"unknown key type: {self._pkey}")
@property
def private_key(self) -> Any:
return self._pkey
@property
def certificate(self) -> Any:
return self._cert
@property
def cert_pem(self) -> bytes:
return self._cert.public_bytes(Encoding.PEM)
@property
def pkey_pem(self) -> bytes:
return self._pkey.private_bytes(
Encoding.PEM,
PrivateFormat.TraditionalOpenSSL if self.key_type.startswith('rsa') else PrivateFormat.PKCS8,
NoEncryption())
@property
def issuer(self) -> Optional['Credentials']:
return self._issuer
def set_store(self, store: 'CertStore'):
self._store = store
def set_files(self, cert_file: str, pkey_file: str = None):
self._cert_file = cert_file
self._pkey_file = pkey_file
@property
def cert_file(self) -> str:
return self._cert_file
@property
def pkey_file(self) -> Optional[str]:
return self._pkey_file
def get_first(self, name) -> Optional['Credentials']:
creds = self._store.get_credentials_for_name(name) if self._store else []
return creds[0] if len(creds) else None
def get_credentials_for_name(self, name) -> List['Credentials']:
return self._store.get_credentials_for_name(name) if self._store else []
def issue_certs(self, specs: List[CertificateSpec],
chain: List['Credentials'] = None) -> List['Credentials']:
return [self.issue_cert(spec=spec, chain=chain) for spec in specs]
def issue_cert(self, spec: CertificateSpec, chain: List['Credentials'] = None) -> 'Credentials':
key_type = spec.key_type if spec.key_type else self.key_type
creds = None
if self._store:
creds = self._store.load_credentials(
name=spec.name, key_type=key_type, single_file=spec.single_file, issuer=self)
if creds is None:
creds = Ngtcp2TestCA.create_credentials(spec=spec, issuer=self, key_type=key_type,
valid_from=spec.valid_from, valid_to=spec.valid_to)
if self._store:
self._store.save(creds, single_file=spec.single_file)
if spec.type == "ca":
self._store.save_chain(creds, "ca", with_root=True)
if spec.sub_specs:
if self._store:
sub_store = CertStore(fpath=os.path.join(self._store.path, creds.name))
creds.set_store(sub_store)
subchain = chain.copy() if chain else []
subchain.append(self)
creds.issue_certs(spec.sub_specs, chain=subchain)
return creds
class CertStore:
def __init__(self, fpath: str):
self._store_dir = fpath
if not os.path.exists(self._store_dir):
os.makedirs(self._store_dir)
self._creds_by_name = {}
@property
def path(self) -> str:
return self._store_dir
def save(self, creds: Credentials, name: str = None,
chain: List[Credentials] = None,
single_file: bool = False) -> None:
name = name if name is not None else creds.name
cert_file = self.get_cert_file(name=name, key_type=creds.key_type)
pkey_file = self.get_pkey_file(name=name, key_type=creds.key_type)
if single_file:
pkey_file = None
with open(cert_file, "wb") as fd:
fd.write(creds.cert_pem)
if chain:
for c in chain:
fd.write(c.cert_pem)
if pkey_file is None:
fd.write(creds.pkey_pem)
if pkey_file is not None:
with open(pkey_file, "wb") as fd:
fd.write(creds.pkey_pem)
creds.set_files(cert_file, pkey_file)
self._add_credentials(name, creds)
def save_chain(self, creds: Credentials, infix: str, with_root=False):
name = creds.name
chain = [creds]
while creds.issuer is not None:
creds = creds.issuer
chain.append(creds)
if not with_root and len(chain) > 1:
chain = chain[:-1]
chain_file = os.path.join(self._store_dir, f'{name}-{infix}.pem')
with open(chain_file, "wb") as fd:
for c in chain:
fd.write(c.cert_pem)
def _add_credentials(self, name: str, creds: Credentials):
if name not in self._creds_by_name:
self._creds_by_name[name] = []
self._creds_by_name[name].append(creds)
def get_credentials_for_name(self, name) -> List[Credentials]:
return self._creds_by_name[name] if name in self._creds_by_name else []
def get_cert_file(self, name: str, key_type=None) -> str:
key_infix = ".{0}".format(key_type) if key_type is not None else ""
return os.path.join(self._store_dir, f'{name}{key_infix}.cert.pem')
def get_pkey_file(self, name: str, key_type=None) -> str:
key_infix = ".{0}".format(key_type) if key_type is not None else ""
return os.path.join(self._store_dir, f'{name}{key_infix}.pkey.pem')
def load_pem_cert(self, fpath: str) -> x509.Certificate:
with open(fpath) as fd:
return x509.load_pem_x509_certificate("".join(fd.readlines()).encode())
def load_pem_pkey(self, fpath: str):
with open(fpath) as fd:
return load_pem_private_key("".join(fd.readlines()).encode(), password=None)
def load_credentials(self, name: str, key_type=None, single_file: bool = False, issuer: Credentials = None):
cert_file = self.get_cert_file(name=name, key_type=key_type)
pkey_file = cert_file if single_file else self.get_pkey_file(name=name, key_type=key_type)
if os.path.isfile(cert_file) and os.path.isfile(pkey_file):
cert = self.load_pem_cert(cert_file)
pkey = self.load_pem_pkey(pkey_file)
creds = Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer)
creds.set_store(self)
creds.set_files(cert_file, pkey_file)
self._add_credentials(name, creds)
return creds
return None
class Ngtcp2TestCA:
@classmethod
def create_root(cls, name: str, store_dir: str, key_type: str = "rsa2048") -> Credentials:
store = CertStore(fpath=store_dir)
creds = store.load_credentials(name="ca", key_type=key_type, issuer=None)
if creds is None:
creds = Ngtcp2TestCA._make_ca_credentials(name=name, key_type=key_type)
store.save(creds, name="ca")
creds.set_store(store)
return creds
@staticmethod
def create_credentials(spec: CertificateSpec, issuer: Credentials, key_type: Any,
valid_from: timedelta = timedelta(days=-1),
valid_to: timedelta = timedelta(days=89),
) -> Credentials:
"""Create a certificate signed by this CA for the given domains.
:returns: the certificate and private key PEM file paths
"""
if spec.domains and len(spec.domains):
creds = Ngtcp2TestCA._make_server_credentials(name=spec.name, domains=spec.domains,
issuer=issuer, valid_from=valid_from,
valid_to=valid_to, key_type=key_type)
elif spec.client:
creds = Ngtcp2TestCA._make_client_credentials(name=spec.name, issuer=issuer,
email=spec.email, valid_from=valid_from,
valid_to=valid_to, key_type=key_type)
elif spec.name:
creds = Ngtcp2TestCA._make_ca_credentials(name=spec.name, issuer=issuer,
valid_from=valid_from, valid_to=valid_to,
key_type=key_type)
else:
raise Exception(f"unrecognized certificate specification: {spec}")
return creds
@staticmethod
def _make_x509_name(org_name: str = None, common_name: str = None, parent: x509.Name = None) -> x509.Name:
name_pieces = []
if org_name:
oid = NameOID.ORGANIZATIONAL_UNIT_NAME if parent else NameOID.ORGANIZATION_NAME
name_pieces.append(x509.NameAttribute(oid, org_name))
elif common_name:
name_pieces.append(x509.NameAttribute(NameOID.COMMON_NAME, common_name))
if parent:
name_pieces.extend([rdn for rdn in parent])
return x509.Name(name_pieces)
@staticmethod
def _make_csr(
subject: x509.Name,
pkey: Any,
issuer_subject: Optional[Credentials],
valid_from_delta: timedelta = None,
valid_until_delta: timedelta = None
):
pubkey = pkey.public_key()
issuer_subject = issuer_subject if issuer_subject is not None else subject
valid_from = datetime.now()
if valid_until_delta is not None:
valid_from += valid_from_delta
valid_until = datetime.now()
if valid_until_delta is not None:
valid_until += valid_until_delta
return (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer_subject)
.public_key(pubkey)
.not_valid_before(valid_from)
.not_valid_after(valid_until)
.serial_number(x509.random_serial_number())
.add_extension(
x509.SubjectKeyIdentifier.from_public_key(pubkey),
critical=False,
)
)
@staticmethod
def _add_ca_usages(csr: Any) -> Any:
return csr.add_extension(
x509.BasicConstraints(ca=True, path_length=9),
critical=True,
).add_extension(
x509.KeyUsage(
digital_signature=True,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=True,
crl_sign=True,
encipher_only=False,
decipher_only=False),
critical=True
).add_extension(
x509.ExtendedKeyUsage([
ExtendedKeyUsageOID.CLIENT_AUTH,
ExtendedKeyUsageOID.SERVER_AUTH,
ExtendedKeyUsageOID.CODE_SIGNING,
]),
critical=True
)
@staticmethod
def _add_leaf_usages(csr: Any, domains: List[str], issuer: Credentials) -> Any:
return csr.add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True,
).add_extension(
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
issuer.certificate.extensions.get_extension_for_class(
x509.SubjectKeyIdentifier).value),
critical=False
).add_extension(
x509.SubjectAlternativeName([x509.DNSName(domain) for domain in domains]),
critical=True,
).add_extension(
x509.ExtendedKeyUsage([
ExtendedKeyUsageOID.SERVER_AUTH,
]),
critical=True
)
@staticmethod
def _add_client_usages(csr: Any, issuer: Credentials, rfc82name: str = None) -> Any:
cert = csr.add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True,
).add_extension(
x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
issuer.certificate.extensions.get_extension_for_class(
x509.SubjectKeyIdentifier).value),
critical=False
)
if rfc82name:
cert.add_extension(
x509.SubjectAlternativeName([x509.RFC822Name(rfc82name)]),
critical=True,
)
cert.add_extension(
x509.ExtendedKeyUsage([
ExtendedKeyUsageOID.CLIENT_AUTH,
]),
critical=True
)
return cert
@staticmethod
def _make_ca_credentials(name, key_type: Any,
issuer: Credentials = None,
valid_from: timedelta = timedelta(days=-1),
valid_to: timedelta = timedelta(days=89),
) -> Credentials:
pkey = _private_key(key_type=key_type)
if issuer is not None:
issuer_subject = issuer.certificate.subject
issuer_key = issuer.private_key
else:
issuer_subject = None
issuer_key = pkey
subject = Ngtcp2TestCA._make_x509_name(org_name=name, parent=issuer.subject if issuer else None)
csr = Ngtcp2TestCA._make_csr(subject=subject,
issuer_subject=issuer_subject, pkey=pkey,
valid_from_delta=valid_from, valid_until_delta=valid_to)
csr = Ngtcp2TestCA._add_ca_usages(csr)
cert = csr.sign(private_key=issuer_key,
algorithm=hashes.SHA256(),
backend=default_backend())
return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer)
@staticmethod
def _make_server_credentials(name: str, domains: List[str], issuer: Credentials,
key_type: Any,
valid_from: timedelta = timedelta(days=-1),
valid_to: timedelta = timedelta(days=89),
) -> Credentials:
name = name
pkey = _private_key(key_type=key_type)
subject = Ngtcp2TestCA._make_x509_name(common_name=name, parent=issuer.subject)
csr = Ngtcp2TestCA._make_csr(subject=subject,
issuer_subject=issuer.certificate.subject, pkey=pkey,
valid_from_delta=valid_from, valid_until_delta=valid_to)
csr = Ngtcp2TestCA._add_leaf_usages(csr, domains=domains, issuer=issuer)
cert = csr.sign(private_key=issuer.private_key,
algorithm=hashes.SHA256(),
backend=default_backend())
return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer)
@staticmethod
def _make_client_credentials(name: str,
issuer: Credentials, email: Optional[str],
key_type: Any,
valid_from: timedelta = timedelta(days=-1),
valid_to: timedelta = timedelta(days=89),
) -> Credentials:
pkey = _private_key(key_type=key_type)
subject = Ngtcp2TestCA._make_x509_name(common_name=name, parent=issuer.subject)
csr = Ngtcp2TestCA._make_csr(subject=subject,
issuer_subject=issuer.certificate.subject, pkey=pkey,
valid_from_delta=valid_from, valid_until_delta=valid_to)
csr = Ngtcp2TestCA._add_client_usages(csr, issuer=issuer, rfc82name=email)
cert = csr.sign(private_key=issuer.private_key,
algorithm=hashes.SHA256(),
backend=default_backend())
return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer)

View File

@ -0,0 +1,187 @@
import logging
import os
import re
import subprocess
from typing import List
import pytest
from .server import ExampleServer, ServerRun
from .certs import Credentials
from .tls import HandShake, HSRecord
from .env import Env, CryptoLib
from .log import LogFile, HexDumpScanner
log = logging.getLogger(__name__)
class ClientRun:
def __init__(self, env: Env, returncode, logfile: LogFile, srun: ServerRun):
self.env = env
self.returncode = returncode
self.logfile = logfile
self.log_lines = logfile.get_recent()
self._data_recs = None
self._hs_recs = None
self._srun = srun
if self.env.verbose > 1:
log.debug(f'read {len(self.log_lines)} lines from {logfile.path}')
@property
def handshake(self) -> List[HSRecord]:
if self._data_recs is None:
crypto_line = re.compile(r'Ordered CRYPTO data in \S+ crypto level')
scanner = HexDumpScanner(source=self.log_lines,
leading_regex=crypto_line)
self._data_recs = [data for data in scanner]
if self.env.verbose > 1:
log.debug(f'detected {len(self._data_recs)} crypto hexdumps '
f'in {self.logfile.path}')
if self._hs_recs is None:
self._hs_recs = [hrec for hrec in HandShake(source=self._data_recs,
verbose=self.env.verbose)]
if self.env.verbose > 1:
log.debug(f'detected {len(self._hs_recs)} crypto '
f'records in {self.logfile.path}')
return self._hs_recs
@property
def hs_stripe(self) -> str:
return ":".join([hrec.name for hrec in self.handshake])
@property
def early_data_rejected(self) -> bool:
for l in self.log_lines:
if re.match(r'^Early data was rejected by server.*', l):
return True
return False
@property
def server(self) -> ServerRun:
return self._srun
def norm_exp(self, c_hs, s_hs, allow_hello_retry=True):
if allow_hello_retry and self.hs_stripe.startswith('HelloRetryRequest:'):
c_hs = "HelloRetryRequest:" + c_hs
s_hs = "ClientHello:" + s_hs
return c_hs, s_hs
def _assert_hs(self, c_hs, s_hs):
if re.match('^'+c_hs, self.hs_stripe) is None:
# what happened?
if self.hs_stripe == '':
# server send nothing
if self.server.hs_stripe == '':
# client send nothing
pytest.fail(f'client did not send a ClientHello"')
else:
# client send sth, but server did not respond
pytest.fail(f'server did not respond to ClientHello: '
f'{self.server.handshake[0].to_text()}"')
else:
pytest.fail(f'Expected "{c_hs}", got "{self.hs_stripe}"')
assert re.match('^'+s_hs+'$', self.server.hs_stripe) is not None, \
f'Expected "{s_hs}", got "{self.server.hs_stripe}"\n'
def assert_non_resume_handshake(self, allow_hello_retry=True):
# for client/server where KEY_SHARE do not match, the hello is retried
c_hs, s_hs = self.norm_exp(
"ServerHello:EncryptedExtensions:(Compressed)?Certificate:CertificateVerify:Finished",
"ClientHello:Finished", allow_hello_retry=allow_hello_retry)
self._assert_hs(c_hs, s_hs)
def assert_resume_handshake(self):
# for client/server where KEY_SHARE do not match, the hello is retried
c_hs, s_hs = self.norm_exp("ServerHello:EncryptedExtensions:Finished",
"ClientHello:Finished")
self._assert_hs(c_hs, s_hs)
def assert_verify_null_handshake(self):
c_hs, s_hs = self.norm_exp(
"ServerHello:EncryptedExtensions:CertificateRequest:(Compressed)?Certificate:CertificateVerify:Finished",
"ClientHello:Certificate:Finished")
self._assert_hs(c_hs, s_hs)
def assert_verify_cert_handshake(self):
c_hs, s_hs = self.norm_exp(
"ServerHello:EncryptedExtensions:CertificateRequest:(Compressed)?Certificate:CertificateVerify:Finished",
"ClientHello:Certificate:CertificateVerify:Finished")
self._assert_hs(c_hs, s_hs)
class ExampleClient:
def __init__(self, env: Env, crypto_lib: str):
self.env = env
self._crypto_lib = crypto_lib
self._path = env.client_path(self._crypto_lib)
self._log_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.log'
self._qlog_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.qlog'
self._session_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.session'
self._tp_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.tp'
self._data_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.data'
@property
def path(self):
return self._path
@property
def crypto_lib(self):
return self._crypto_lib
@property
def uses_cipher_config(self):
return CryptoLib.uses_cipher_config(self.crypto_lib)
def supports_cipher(self, cipher):
return CryptoLib.supports_cipher(self.crypto_lib, cipher)
def exists(self):
return os.path.isfile(self.path)
def clear_session(self):
if os.path.isfile(self._session_path):
os.remove(self._session_path)
if os.path.isfile(self._tp_path):
os.remove(self._tp_path)
def http_get(self, server: ExampleServer, url: str, extra_args: List[str] = None,
use_session=False, data=None,
credentials: Credentials = None,
ciphers: str = None):
args = [
self.path, '--exit-on-all-streams-close',
f'--qlog-file={self._qlog_path}'
]
if use_session:
args.append(f'--session-file={self._session_path}')
args.append(f'--tp-file={self._tp_path}')
if data is not None:
with open(self._data_path, 'w') as fd:
fd.write(data)
args.append(f'--data={self._data_path}')
if ciphers is not None:
ciphers = CryptoLib.adjust_ciphers(self.crypto_lib, ciphers)
args.append(f'--ciphers={ciphers}')
if credentials is not None:
args.append(f'--key={credentials.pkey_file}')
args.append(f'--cert={credentials.cert_file}')
if extra_args is not None:
args.extend(extra_args)
args.extend([
'localhost', str(self.env.examples_port),
url
])
if os.path.isfile(self._qlog_path):
os.remove(self._qlog_path)
with open(self._log_path, 'w') as log_file:
logfile = LogFile(path=self._log_path)
server.log.advance()
process = subprocess.Popen(args=args, text=True,
stdout=log_file, stderr=log_file)
process.wait()
return ClientRun(env=self.env, returncode=process.returncode,
logfile=logfile, srun=server.get_run())

View File

@ -0,0 +1,191 @@
import logging
import os
from configparser import ConfigParser, ExtendedInterpolation
from typing import Dict, Optional
from .certs import CertificateSpec, Ngtcp2TestCA, Credentials
log = logging.getLogger(__name__)
class CryptoLib:
IGNORES_CIPHER_CONFIG = [
'picotls', 'boringssl'
]
UNSUPPORTED_CIPHERS = {
'wolfssl': [
'TLS_AES_128_CCM_SHA256', # no plans to
],
'picotls': [
'TLS_AES_128_CCM_SHA256', # no plans to
],
'boringssl': [
'TLS_AES_128_CCM_SHA256', # no plans to
]
}
GNUTLS_CIPHERS = {
'TLS_AES_128_GCM_SHA256': 'AES-128-GCM',
'TLS_AES_256_GCM_SHA384': 'AES-256-GCM',
'TLS_CHACHA20_POLY1305_SHA256': 'CHACHA20-POLY1305',
'TLS_AES_128_CCM_SHA256': 'AES-128-CCM',
}
@classmethod
def uses_cipher_config(cls, crypto_lib):
return crypto_lib not in cls.IGNORES_CIPHER_CONFIG
@classmethod
def supports_cipher(cls, crypto_lib, cipher):
return crypto_lib not in cls.UNSUPPORTED_CIPHERS or \
cipher not in cls.UNSUPPORTED_CIPHERS[crypto_lib]
@classmethod
def adjust_ciphers(cls, crypto_lib, ciphers: str) -> str:
if crypto_lib == 'gnutls':
gciphers = "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL"
for cipher in ciphers.split(':'):
gciphers += f':+{cls.GNUTLS_CIPHERS[cipher]}'
return gciphers
return ciphers
def init_config_from(conf_path):
if os.path.isfile(conf_path):
config = ConfigParser(interpolation=ExtendedInterpolation())
config.read(conf_path)
return config
return None
TESTS_PATH = os.path.dirname(os.path.dirname(__file__))
EXAMPLES_PATH = os.path.dirname(TESTS_PATH)
DEF_CONFIG = init_config_from(os.path.join(TESTS_PATH, 'config.ini'))
class Env:
@classmethod
def get_crypto_libs(cls, configurable_ciphers=None):
names = [name for name in DEF_CONFIG['examples']
if DEF_CONFIG['examples'][name] == 'yes']
if configurable_ciphers is not None:
names = [n for n in names if CryptoLib.uses_cipher_config(n)]
return names
def __init__(self, examples_dir=None, tests_dir=None, config=None,
pytestconfig=None):
self._verbose = pytestconfig.option.verbose if pytestconfig is not None else 0
self._examples_dir = examples_dir if examples_dir is not None else EXAMPLES_PATH
self._tests_dir = examples_dir if tests_dir is not None else TESTS_PATH
self._gen_dir = os.path.join(self._tests_dir, 'gen')
self.config = config if config is not None else DEF_CONFIG
self._version = self.config['ngtcp2']['version']
self._crypto_libs = [name for name in self.config['examples']
if self.config['examples'][name] == 'yes']
self._clients = [self.config['clients'][lib] for lib in self._crypto_libs
if lib in self.config['clients']]
self._servers = [self.config['servers'][lib] for lib in self._crypto_libs
if lib in self.config['servers']]
self._examples_pem = {
'key': 'xxx',
'cert': 'xxx',
}
self._htdocs_dir = os.path.join(self._gen_dir, 'htdocs')
self._tld = 'tests.ngtcp2.nghttp2.org'
self._example_domain = f"one.{self._tld}"
self._ca = None
self._cert_specs = [
CertificateSpec(domains=[self._example_domain], key_type='rsa2048'),
CertificateSpec(name="clientsX", sub_specs=[
CertificateSpec(name="user1", client=True),
]),
]
def issue_certs(self):
if self._ca is None:
self._ca = Ngtcp2TestCA.create_root(name=self._tld,
store_dir=os.path.join(self.gen_dir, 'ca'),
key_type="rsa2048")
self._ca.issue_certs(self._cert_specs)
def setup(self):
os.makedirs(self._gen_dir, exist_ok=True)
os.makedirs(self._htdocs_dir, exist_ok=True)
self.issue_certs()
def get_server_credentials(self) -> Optional[Credentials]:
creds = self.ca.get_credentials_for_name(self._example_domain)
if len(creds) > 0:
return creds[0]
return None
@property
def verbose(self) -> int:
return self._verbose
@property
def version(self) -> str:
return self._version
@property
def gen_dir(self) -> str:
return self._gen_dir
@property
def ca(self):
return self._ca
@property
def htdocs_dir(self) -> str:
return self._htdocs_dir
@property
def example_domain(self) -> str:
return self._example_domain
@property
def examples_dir(self) -> str:
return self._examples_dir
@property
def examples_port(self) -> int:
return int(self.config['examples']['port'])
@property
def examples_pem(self) -> Dict[str, str]:
return self._examples_pem
@property
def crypto_libs(self):
return self._crypto_libs
@property
def clients(self):
return self._clients
@property
def servers(self):
return self._servers
def client_name(self, crypto_lib):
if crypto_lib in self.config['clients']:
return self.config['clients'][crypto_lib]
return None
def client_path(self, crypto_lib):
cname = self.client_name(crypto_lib)
if cname is not None:
return os.path.join(self.examples_dir, cname)
return None
def server_name(self, crypto_lib):
if crypto_lib in self.config['servers']:
return self.config['servers'][crypto_lib]
return None
def server_path(self, crypto_lib):
sname = self.server_name(crypto_lib)
if sname is not None:
return os.path.join(self.examples_dir, sname)
return None

View File

@ -0,0 +1,101 @@
import binascii
import os
import re
import sys
import time
from datetime import timedelta, datetime
from io import SEEK_END
from typing import List
class LogFile:
def __init__(self, path: str):
self._path = path
self._start_pos = 0
self._last_pos = self._start_pos
@property
def path(self) -> str:
return self._path
def reset(self):
self._start_pos = 0
self._last_pos = self._start_pos
def advance(self) -> None:
if os.path.isfile(self._path):
with open(self._path) as fd:
self._start_pos = fd.seek(0, SEEK_END)
def get_recent(self, advance=True) -> List[str]:
lines = []
if os.path.isfile(self._path):
with open(self._path) as fd:
fd.seek(self._last_pos, os.SEEK_SET)
for line in fd:
lines.append(line)
if advance:
self._last_pos = fd.tell()
return lines
def scan_recent(self, pattern: re, timeout=10) -> bool:
if not os.path.isfile(self.path):
return False
with open(self.path) as fd:
end = datetime.now() + timedelta(seconds=timeout)
while True:
fd.seek(self._last_pos, os.SEEK_SET)
for line in fd:
if pattern.match(line):
return True
if datetime.now() > end:
raise TimeoutError(f"pattern not found in error log after {timeout} seconds")
time.sleep(.1)
return False
class HexDumpScanner:
def __init__(self, source, leading_regex=None):
self._source = source
self._leading_regex = leading_regex
def __iter__(self):
data = b''
offset = 0 if self._leading_regex is None else -1
idx = 0
for l in self._source:
if offset == -1:
pass
elif offset == 0:
# possible start of a hex dump
m = re.match(r'^\s*0+(\s+-)?((\s+[0-9a-f]{2}){1,16})(\s+.*)$',
l, re.IGNORECASE)
if m:
data = binascii.unhexlify(re.sub(r'\s+', '', m.group(2)))
offset = 16
idx = 1
continue
else:
# possible continuation of a hexdump
m = re.match(r'^\s*([0-9a-f]+)(\s+-)?((\s+[0-9a-f]{2}){1,16})'
r'(\s+.*)$', l, re.IGNORECASE)
if m:
loffset = int(m.group(1), 16)
if loffset == offset or loffset == idx:
data += binascii.unhexlify(re.sub(r'\s+', '',
m.group(3)))
offset += 16
idx += 1
continue
else:
sys.stderr.write(f'wrong offset {loffset}, expected {offset} or {idx}\n')
# not a hexdump line, produce any collected data
if len(data) > 0:
yield data
data = b''
offset = 0 if self._leading_regex is None \
or self._leading_regex.match(l) else -1
if len(data) > 0:
yield data

View File

@ -0,0 +1,137 @@
import logging
import os
import re
import subprocess
import time
from datetime import datetime, timedelta
from threading import Thread
from .tls import HandShake
from .env import Env, CryptoLib
from .log import LogFile, HexDumpScanner
log = logging.getLogger(__name__)
class ServerRun:
def __init__(self, env: Env, logfile: LogFile):
self.env = env
self._logfile = logfile
self.log_lines = self._logfile.get_recent()
self._data_recs = None
self._hs_recs = None
if self.env.verbose > 1:
log.debug(f'read {len(self.log_lines)} lines from {logfile.path}')
@property
def handshake(self):
if self._data_recs is None:
self._data_recs = [data for data in HexDumpScanner(source=self.log_lines)]
if self.env.verbose > 1:
log.debug(f'detected {len(self._data_recs)} hexdumps '
f'in {self._logfile.path}')
if self._hs_recs is None:
self._hs_recs = [hrec for hrec in HandShake(source=self._data_recs,
verbose=self.env.verbose)]
if self.env.verbose > 1:
log.debug(f'detected {len(self._hs_recs)} crypto records '
f'in {self._logfile.path}')
return self._hs_recs
@property
def hs_stripe(self):
return ":".join([hrec.name for hrec in self.handshake])
def monitor_proc(env: Env, proc):
_env = env
proc.wait()
class ExampleServer:
def __init__(self, env: Env, crypto_lib: str, verify_client=False):
self.env = env
self._crypto_lib = crypto_lib
self._path = env.server_path(self._crypto_lib)
self._logpath = f'{self.env.gen_dir}/{self._crypto_lib}-server.log'
self._log = LogFile(path=self._logpath)
self._logfile = None
self._process = None
self._verify_client = verify_client
@property
def path(self):
return self._path
@property
def crypto_lib(self):
return self._crypto_lib
@property
def uses_cipher_config(self):
return CryptoLib.uses_cipher_config(self.crypto_lib)
def supports_cipher(self, cipher):
return CryptoLib.supports_cipher(self.crypto_lib, cipher)
@property
def log(self):
return self._log
def exists(self):
return os.path.isfile(self.path)
def start(self):
if self._process is not None:
return False
creds = self.env.get_server_credentials()
assert creds
args = [
self.path,
f'--htdocs={self.env.htdocs_dir}',
]
if self._verify_client:
args.append('--verify-client')
args.extend([
'*', str(self.env.examples_port),
creds.pkey_file, creds.cert_file
])
self._logfile = open(self._logpath, 'w')
self._process = subprocess.Popen(args=args, text=True,
stdout=self._logfile, stderr=self._logfile)
t = Thread(target=monitor_proc, daemon=True, args=(self.env, self._process))
t.start()
timeout = 5
end = datetime.now() + timedelta(seconds=timeout)
while True:
if self._process.poll():
return False
try:
if self.log.scan_recent(pattern=re.compile(r'^Using document root'), timeout=0.5):
break
except TimeoutError:
pass
if datetime.now() > end:
raise TimeoutError(f"pattern not found in error log after {timeout} seconds")
self.log.advance()
return True
def stop(self):
if self._process:
self._process.terminate()
self._process = None
if self._logfile:
self._logfile.close()
self._logfile = None
return True
def restart(self):
self.stop()
self._log.reset()
return self.start()
def get_run(self) -> ServerRun:
return ServerRun(env=self.env, logfile=self.log)

View File

@ -0,0 +1,983 @@
import binascii
import logging
import sys
from collections.abc import Iterator
from typing import Dict, Any, Iterable
log = logging.getLogger(__name__)
class ParseError(Exception):
pass
def _get_int(d, n):
if len(d) < n:
raise ParseError(f'get_int: {n} bytes needed, but data is {d}')
if n == 1:
dlen = d[0]
else:
dlen = int.from_bytes(d[0:n], byteorder='big')
return d[n:], dlen
def _get_field(d, dlen):
if dlen > 0:
if len(d) < dlen:
raise ParseError(f'field len={dlen}, but data len={len(d)}')
field = d[0:dlen]
return d[dlen:], field
return d, b''
def _get_len_field(d, n):
d, dlen = _get_int(d, n)
return _get_field(d, dlen)
# d are bytes that start with a quic variable length integer
def _get_qint(d):
i = d[0] & 0xc0
if i == 0:
return d[1:], int(d[0])
elif i == 0x40:
ndata = bytearray(d[0:2])
d = d[2:]
ndata[0] = ndata[0] & ~0xc0
return d, int.from_bytes(ndata, byteorder='big')
elif i == 0x80:
ndata = bytearray(d[0:4])
d = d[4:]
ndata[0] = ndata[0] & ~0xc0
return d, int.from_bytes(ndata, byteorder='big')
else:
ndata = bytearray(d[0:8])
d = d[8:]
ndata[0] = ndata[0] & ~0xc0
return d, int.from_bytes(ndata, byteorder='big')
class TlsSupportedGroups:
NAME_BY_ID = {
0: 'Reserved',
1: 'sect163k1',
2: 'sect163r1',
3: 'sect163r2',
4: 'sect193r1',
5: 'sect193r2',
6: 'sect233k1',
7: 'sect233r1',
8: 'sect239k1',
9: 'sect283k1',
10: 'sect283r1',
11: 'sect409k1',
12: 'sect409r1',
13: 'sect571k1',
14: 'sect571r1',
15: 'secp160k1',
16: 'secp160r1',
17: 'secp160r2',
18: 'secp192k1',
19: 'secp192r1',
20: 'secp224k1',
21: 'secp224r1',
22: 'secp256k1',
23: 'secp256r1',
24: 'secp384r1',
25: 'secp521r1',
26: 'brainpoolP256r1',
27: 'brainpoolP384r1',
28: 'brainpoolP512r1',
29: 'x25519',
30: 'x448',
31: 'brainpoolP256r1tls13',
32: 'brainpoolP384r1tls13',
33: 'brainpoolP512r1tls13',
34: 'GC256A',
35: 'GC256B',
36: 'GC256C',
37: 'GC256D',
38: 'GC512A',
39: 'GC512B',
40: 'GC512C',
41: 'curveSM2',
}
@classmethod
def name(cls, gid):
if gid in cls.NAME_BY_ID:
return f'{cls.NAME_BY_ID[gid]}(0x{gid:0x})'
return f'0x{gid:0x}'
class TlsSignatureScheme:
NAME_BY_ID = {
0x0201: 'rsa_pkcs1_sha1',
0x0202: 'Reserved',
0x0203: 'ecdsa_sha1',
0x0401: 'rsa_pkcs1_sha256',
0x0403: 'ecdsa_secp256r1_sha256',
0x0420: 'rsa_pkcs1_sha256_legacy',
0x0501: 'rsa_pkcs1_sha384',
0x0503: 'ecdsa_secp384r1_sha384',
0x0520: 'rsa_pkcs1_sha384_legacy',
0x0601: 'rsa_pkcs1_sha512',
0x0603: 'ecdsa_secp521r1_sha512',
0x0620: 'rsa_pkcs1_sha512_legacy',
0x0704: 'eccsi_sha256',
0x0705: 'iso_ibs1',
0x0706: 'iso_ibs2',
0x0707: 'iso_chinese_ibs',
0x0708: 'sm2sig_sm3',
0x0709: 'gostr34102012_256a',
0x070A: 'gostr34102012_256b',
0x070B: 'gostr34102012_256c',
0x070C: 'gostr34102012_256d',
0x070D: 'gostr34102012_512a',
0x070E: 'gostr34102012_512b',
0x070F: 'gostr34102012_512c',
0x0804: 'rsa_pss_rsae_sha256',
0x0805: 'rsa_pss_rsae_sha384',
0x0806: 'rsa_pss_rsae_sha512',
0x0807: 'ed25519',
0x0808: 'ed448',
0x0809: 'rsa_pss_pss_sha256',
0x080A: 'rsa_pss_pss_sha384',
0x080B: 'rsa_pss_pss_sha512',
0x081A: 'ecdsa_brainpoolP256r1tls13_sha256',
0x081B: 'ecdsa_brainpoolP384r1tls13_sha384',
0x081C: 'ecdsa_brainpoolP512r1tls13_sha512',
}
@classmethod
def name(cls, gid):
if gid in cls.NAME_BY_ID:
return f'{cls.NAME_BY_ID[gid]}(0x{gid:0x})'
return f'0x{gid:0x}'
class TlsCipherSuites:
NAME_BY_ID = {
0x1301: 'TLS_AES_128_GCM_SHA256',
0x1302: 'TLS_AES_256_GCM_SHA384',
0x1303: 'TLS_CHACHA20_POLY1305_SHA256',
0x1304: 'TLS_AES_128_CCM_SHA256',
0x1305: 'TLS_AES_128_CCM_8_SHA256',
0x00ff: 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV',
}
@classmethod
def name(cls, cid):
if cid in cls.NAME_BY_ID:
return f'{cls.NAME_BY_ID[cid]}(0x{cid:0x})'
return f'Cipher(0x{cid:0x})'
class PskKeyExchangeMode:
NAME_BY_ID = {
0x00: 'psk_ke',
0x01: 'psk_dhe_ke',
}
@classmethod
def name(cls, gid):
if gid in cls.NAME_BY_ID:
return f'{cls.NAME_BY_ID[gid]}(0x{gid:0x})'
return f'0x{gid:0x}'
class QuicTransportParam:
NAME_BY_ID = {
0x00: 'original_destination_connection_id',
0x01: 'max_idle_timeout',
0x02: 'stateless_reset_token',
0x03: 'max_udp_payload_size',
0x04: 'initial_max_data',
0x05: 'initial_max_stream_data_bidi_local',
0x06: 'initial_max_stream_data_bidi_remote',
0x07: 'initial_max_stream_data_uni',
0x08: 'initial_max_streams_bidi',
0x09: 'initial_max_streams_uni',
0x0a: 'ack_delay_exponent',
0x0b: 'max_ack_delay',
0x0c: 'disable_active_migration',
0x0d: 'preferred_address',
0x0e: 'active_connection_id_limit',
0x0f: 'initial_source_connection_id',
0x10: 'retry_source_connection_id',
}
TYPE_BY_ID = {
0x00: bytes,
0x01: int,
0x02: bytes,
0x03: int,
0x04: int,
0x05: int,
0x06: int,
0x07: int,
0x08: int,
0x09: int,
0x0a: int,
0x0b: int,
0x0c: int,
0x0d: bytes,
0x0e: int,
0x0f: bytes,
0x10: bytes,
}
@classmethod
def name(cls, cid):
if cid in cls.NAME_BY_ID:
return f'{cls.NAME_BY_ID[cid]}(0x{cid:0x})'
return f'QuicTP(0x{cid:0x})'
@classmethod
def is_qint(cls, cid):
if cid in cls.TYPE_BY_ID:
return cls.TYPE_BY_ID[cid] == int
return False
class Extension:
def __init__(self, eid, name, edata, hsid):
self._eid = eid
self._name = name
self._edata = edata
self._hsid = hsid
@property
def data(self):
return self._edata
@property
def hsid(self):
return self._hsid
def to_json(self):
jdata = {
'id': self._eid,
'name': self._name,
}
if len(self.data) > 0:
jdata['data'] = binascii.hexlify(self.data).decode()
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
s = f'{ind}{self._name}(0x{self._eid:0x})'
if len(self._edata):
s += f'\n{ind} data({len(self._edata)}): ' \
f'{binascii.hexlify(self._edata).decode()}'
return s
class ExtSupportedGroups(Extension):
def __init__(self, eid, name, edata, hsid):
super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
d = edata
self._groups = []
while len(d) > 0:
d, gid = _get_int(d, 2)
self._groups.append(gid)
def to_json(self):
jdata = {
'id': self._eid,
'name': self._name,
}
if len(self._groups):
jdata['groups'] = [TlsSupportedGroups.name(gid)
for gid in self._groups]
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
gnames = [TlsSupportedGroups.name(gid) for gid in self._groups]
s = f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(gnames)}'
return s
class ExtKeyShare(Extension):
def __init__(self, eid, name, edata, hsid):
super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
d = self.data
self._keys = []
self._group = None
self._pubkey = None
if self.hsid == 2: # ServerHello
# single key share (group, pubkey)
d, self._group = _get_int(d, 2)
d, self._pubkey = _get_len_field(d, 2)
elif self.hsid == 6: # HelloRetryRequest
assert len(d) == 2
d, self._group = _get_int(d, 2)
else:
# list if key shares (group, pubkey)
d, shares = _get_len_field(d, 2)
while len(shares) > 0:
shares, group = _get_int(shares, 2)
shares, pubkey = _get_len_field(shares, 2)
self._keys.append({
'group': TlsSupportedGroups.name(group),
'pubkey': binascii.hexlify(pubkey).decode()
})
def to_json(self):
jdata = super().to_json()
if self._group is not None:
jdata['group'] = TlsSupportedGroups.name(self._group)
if self._pubkey is not None:
jdata['pubkey'] = binascii.hexlify(self._pubkey).decode()
if len(self._keys) > 0:
jdata['keys'] = self._keys
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
s = f'{ind}{self._name}(0x{self._eid:0x})'
if self._group is not None:
s += f'\n{ind} group: {TlsSupportedGroups.name(self._group)}'
if self._pubkey is not None:
s += f'\n{ind} pubkey: {binascii.hexlify(self._pubkey).decode()}'
if len(self._keys) > 0:
for idx, key in enumerate(self._keys):
s += f'\n{ind} {idx}: {key["group"]}, {key["pubkey"]}'
return s
class ExtSNI(Extension):
def __init__(self, eid, name, edata, hsid):
super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
d = self.data
self._indicators = []
while len(d) > 0:
d, entry = _get_len_field(d, 2)
entry, stype = _get_int(entry, 1)
entry, sname = _get_len_field(entry, 2)
self._indicators.append({
'type': stype,
'name': sname.decode()
})
def to_json(self):
jdata = super().to_json()
for i in self._indicators:
if i['type'] == 0:
jdata['host_name'] = i['name']
else:
jdata[f'0x{i["type"]}'] = i['name']
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
s = f'{ind}{self._name}(0x{self._eid:0x})'
if len(self._indicators) == 1 and self._indicators[0]['type'] == 0:
s += f': {self._indicators[0]["name"]}'
else:
for i in self._indicators:
ikey = 'host_name' if i["type"] == 0 else f'type(0x{i["type"]:0x}'
s += f'\n{ind} {ikey}: {i["name"]}'
return s
class ExtALPN(Extension):
def __init__(self, eid, name, edata, hsid):
super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
d = self.data
d, list_len = _get_int(d, 2)
self._protocols = []
while len(d) > 0:
d, proto = _get_len_field(d, 1)
self._protocols.append(proto.decode())
def to_json(self):
jdata = super().to_json()
if len(self._protocols) == 1:
jdata['alpn'] = self._protocols[0]
else:
jdata['alpn'] = self._protocols
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
return f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(self._protocols)}'
class ExtEarlyData(Extension):
def __init__(self, eid, name, edata, hsid):
super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
self._max_size = None
d = self.data
if hsid == 4: # SessionTicket
assert len(d) == 4, f'expected 4, len is {len(d)} data={d}'
d, self._max_size = _get_int(d, 4)
else:
assert len(d) == 0
def to_json(self):
jdata = super().to_json()
if self._max_size is not None:
jdata['max_size'] = self._max_size
return jdata
class ExtSignatureAlgorithms(Extension):
def __init__(self, eid, name, edata, hsid):
super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
d = self.data
d, list_len = _get_int(d, 2)
self._algos = []
while len(d) > 0:
d, algo = _get_int(d, 2)
self._algos.append(TlsSignatureScheme.name(algo))
def to_json(self):
jdata = super().to_json()
if len(self._algos) > 0:
jdata['algorithms'] = self._algos
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
return f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(self._algos)}'
class ExtPSKExchangeModes(Extension):
def __init__(self, eid, name, edata, hsid):
super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
d = self.data
d, list_len = _get_int(d, 1)
self._modes = []
while len(d) > 0:
d, mode = _get_int(d, 1)
self._modes.append(PskKeyExchangeMode.name(mode))
def to_json(self):
jdata = super().to_json()
jdata['modes'] = self._modes
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
return f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(self._modes)}'
class ExtPreSharedKey(Extension):
def __init__(self, eid, name, edata, hsid):
super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
self._kid = None
self._identities = None
self._binders = None
d = self.data
if hsid == 1: # client hello
d, idata = _get_len_field(d, 2)
self._identities = []
while len(idata):
idata, identity = _get_len_field(idata, 2)
idata, obfs_age = _get_int(idata, 4)
self._identities.append({
'id': binascii.hexlify(identity).decode(),
'age': obfs_age,
})
d, binders = _get_len_field(d, 2)
self._binders = []
while len(binders) > 0:
binders, hmac = _get_len_field(binders, 1)
self._binders.append(binascii.hexlify(hmac).decode())
assert len(d) == 0
else:
d, self._kid = _get_int(d, 2)
def to_json(self):
jdata = super().to_json()
if self.hsid == 1:
jdata['identities'] = self._identities
jdata['binders'] = self._binders
else:
jdata['identity'] = self._kid
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
s = f'{ind}{self._name}(0x{self._hsid:0x})'
if self.hsid == 1:
for idx, i in enumerate(self._identities):
s += f'\n{ind} {idx}: {i["id"]} ({i["age"]})'
s += f'\n{ind} binders: {self._binders}'
else:
s += f'\n{ind} identity: {self._kid}'
return s
class ExtSupportedVersions(Extension):
def __init__(self, eid, name, edata, hsid):
super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
d = self.data
self._versions = []
if hsid == 1: # client hello
d, list_len = _get_int(d, 1)
while len(d) > 0:
d, version = _get_int(d, 2)
self._versions.append(f'0x{version:0x}')
else:
d, version = _get_int(d, 2)
self._versions.append(f'0x{version:0x}')
def to_json(self):
jdata = super().to_json()
if len(self._versions) == 1:
jdata['version'] = self._versions[0]
else:
jdata['versions'] = self._versions
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
return f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(self._versions)}'
class ExtQuicTP(Extension):
def __init__(self, eid, name, edata, hsid):
super().__init__(eid=eid, name=name, edata=edata, hsid=hsid)
d = self.data
self._params = []
while len(d) > 0:
d, ptype = _get_qint(d)
d, plen = _get_qint(d)
d, pvalue = _get_field(d, plen)
if QuicTransportParam.is_qint(ptype):
_, pvalue = _get_qint(pvalue)
else:
pvalue = binascii.hexlify(pvalue).decode()
self._params.append({
'key': QuicTransportParam.name(ptype),
'value': pvalue,
})
def to_json(self):
jdata = super().to_json()
jdata['params'] = self._params
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
s = f'{ind}{self._name}(0x{self._eid:0x})'
for p in self._params:
s += f'\n{ind} {p["key"]}: {p["value"]}'
return s
class TlsExtensions:
EXT_TYPES = [
(0x00, 'SNI', ExtSNI),
(0x01, 'MAX_FRAGMENT_LENGTH', Extension),
(0x03, 'TRUSTED_CA_KEYS', Extension),
(0x04, 'TRUNCATED_HMAC', Extension),
(0x05, 'OSCP_STATUS_REQUEST', Extension),
(0x0a, 'SUPPORTED_GROUPS', ExtSupportedGroups),
(0x0b, 'EC_POINT_FORMATS', Extension),
(0x0d, 'SIGNATURE_ALGORITHMS', ExtSignatureAlgorithms),
(0x0e, 'USE_SRTP', Extension),
(0x10, 'ALPN', ExtALPN),
(0x11, 'STATUS_REQUEST_V2', Extension),
(0x16, 'ENCRYPT_THEN_MAC', Extension),
(0x17, 'EXTENDED_MASTER_SECRET', Extension),
(0x23, 'SESSION_TICKET', Extension),
(0x29, 'PRE_SHARED_KEY', ExtPreSharedKey),
(0x2a, 'EARLY_DATA', ExtEarlyData),
(0x2b, 'SUPPORTED_VERSIONS', ExtSupportedVersions),
(0x2c, 'COOKIE', Extension),
(0x2d, 'PSK_KEY_EXCHANGE_MODES', ExtPSKExchangeModes),
(0x31, 'POST_HANDSHAKE_AUTH', Extension),
(0x32, 'SIGNATURE_ALGORITHMS_CERT', Extension),
(0x33, 'KEY_SHARE', ExtKeyShare),
(0x39, 'QUIC_TP_PARAMS', ExtQuicTP),
(0xff01, 'RENEGOTIATION_INFO', Extension),
]
NAME_BY_ID = {}
CLASS_BY_ID = {}
@classmethod
def init(cls):
for (eid, name, ecls) in cls.EXT_TYPES:
cls.NAME_BY_ID[eid] = name
cls.CLASS_BY_ID[eid] = ecls
@classmethod
def from_data(cls, hsid, data):
exts = []
d = data
while len(d):
d, eid = _get_int(d, 2)
d, elen = _get_int(d, 2)
d, edata = _get_field(d, elen)
if eid in cls.NAME_BY_ID:
ename = cls.NAME_BY_ID[eid]
ecls = cls.CLASS_BY_ID[eid]
exts.append(ecls(eid=eid, name=ename, edata=edata, hsid=hsid))
else:
exts.append(Extension(eid=eid, name=f'(0x{eid:0x})',
edata=edata, hsid=hsid))
return exts
TlsExtensions.init()
class HSRecord:
def __init__(self, hsid: int, name: str, data):
self._hsid = hsid
self._name = name
self._data = data
@property
def hsid(self):
return self._hsid
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
@property
def data(self):
return self._data
def __repr__(self):
return f'{self.name}[{binascii.hexlify(self._data).decode()}]'
def to_json(self) -> Dict[str, Any]:
return {
'name': self.name,
'data': binascii.hexlify(self._data).decode(),
}
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
return f'{ind}{self._name}\n'\
f'{ind} id: 0x{self._hsid:0x}\n'\
f'{ind} data({len(self._data)}): '\
f'{binascii.hexlify(self._data).decode()}'
class ClientHello(HSRecord):
def __init__(self, hsid: int, name: str, data):
super().__init__(hsid=hsid, name=name, data=data)
d = data
d, self._version = _get_int(d, 2)
d, self._random = _get_field(d, 32)
d, self._session_id = _get_len_field(d, 1)
self._ciphers = []
d, ciphers = _get_len_field(d, 2)
while len(ciphers):
ciphers, cipher = _get_int(ciphers, 2)
self._ciphers.append(TlsCipherSuites.name(cipher))
d, comps = _get_len_field(d, 1)
self._compressions = [int(c) for c in comps]
d, edata = _get_len_field(d, 2)
self._extensions = TlsExtensions.from_data(hsid, edata)
def to_json(self):
jdata = super().to_json()
jdata['version'] = f'0x{self._version:0x}'
jdata['random'] = f'{binascii.hexlify(self._random).decode()}'
jdata['session_id'] = binascii.hexlify(self._session_id).decode()
jdata['ciphers'] = self._ciphers
jdata['compressions'] = self._compressions
jdata['extensions'] = [ext.to_json() for ext in self._extensions]
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
return super().to_text(indent=indent) + '\n'\
f'{ind} version: 0x{self._version:0x}\n'\
f'{ind} random: {binascii.hexlify(self._random).decode()}\n' \
f'{ind} session_id: {binascii.hexlify(self._session_id).decode()}\n' \
f'{ind} ciphers: {", ".join(self._ciphers)}\n'\
f'{ind} compressions: {self._compressions}\n'\
f'{ind} extensions: \n' + '\n'.join(
[ext.to_text(indent=indent+4) for ext in self._extensions])
class ServerHello(HSRecord):
HELLO_RETRY_RANDOM = binascii.unhexlify(
'CF21AD74E59A6111BE1D8C021E65B891C2A211167ABB8C5E079E09E2C8A8339C'
)
def __init__(self, hsid: int, name: str, data):
super().__init__(hsid=hsid, name=name, data=data)
d = data
d, self._version = _get_int(d, 2)
d, self._random = _get_field(d, 32)
if self._random == self.HELLO_RETRY_RANDOM:
self.name = 'HelloRetryRequest'
hsid = 6
d, self._session_id = _get_len_field(d, 1)
d, cipher = _get_int(d, 2)
self._cipher = TlsCipherSuites.name(cipher)
d, self._compression = _get_int(d, 1)
d, edata = _get_len_field(d, 2)
self._extensions = TlsExtensions.from_data(hsid, edata)
def to_json(self):
jdata = super().to_json()
jdata['version'] = f'0x{self._version:0x}'
jdata['random'] = f'{binascii.hexlify(self._random).decode()}'
jdata['session_id'] = binascii.hexlify(self._session_id).decode()
jdata['cipher'] = self._cipher
jdata['compression'] = int(self._compression)
jdata['extensions'] = [ext.to_json() for ext in self._extensions]
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
return super().to_text(indent=indent) + '\n'\
f'{ind} version: 0x{self._version:0x}\n'\
f'{ind} random: {binascii.hexlify(self._random).decode()}\n' \
f'{ind} session_id: {binascii.hexlify(self._session_id).decode()}\n' \
f'{ind} cipher: {self._cipher}\n'\
f'{ind} compression: {int(self._compression)}\n'\
f'{ind} extensions: \n' + '\n'.join(
[ext.to_text(indent=indent+4) for ext in self._extensions])
class EncryptedExtensions(HSRecord):
def __init__(self, hsid: int, name: str, data):
super().__init__(hsid=hsid, name=name, data=data)
d = data
d, edata = _get_len_field(d, 2)
self._extensions = TlsExtensions.from_data(hsid, edata)
def to_json(self):
jdata = super().to_json()
jdata['extensions'] = [ext.to_json() for ext in self._extensions]
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
return super().to_text(indent=indent) + '\n'\
f'{ind} extensions: \n' + '\n'.join(
[ext.to_text(indent=indent+4) for ext in self._extensions])
class CertificateRequest(HSRecord):
def __init__(self, hsid: int, name: str, data):
super().__init__(hsid=hsid, name=name, data=data)
d = data
d, self._context = _get_int(d, 1)
d, edata = _get_len_field(d, 2)
self._extensions = TlsExtensions.from_data(hsid, edata)
def to_json(self):
jdata = super().to_json()
jdata['context'] = self._context
jdata['extensions'] = [ext.to_json() for ext in self._extensions]
return jdata
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
return super().to_text(indent=indent) + '\n'\
f'{ind} context: {self._context}\n'\
f'{ind} extensions: \n' + '\n'.join(
[ext.to_text(indent=indent+4) for ext in self._extensions])
class Certificate(HSRecord):
def __init__(self, hsid: int, name: str, data):
super().__init__(hsid=hsid, name=name, data=data)
d = data
d, self._context = _get_int(d, 1)
d, clist = _get_len_field(d, 3)
self._cert_entries = []
while len(clist) > 0:
clist, cert_data = _get_len_field(clist, 3)
clist, cert_exts = _get_len_field(clist, 2)
exts = TlsExtensions.from_data(hsid, cert_exts)
self._cert_entries.append({
'cert': binascii.hexlify(cert_data).decode(),
'extensions': exts,
})
def to_json(self):
jdata = super().to_json()
jdata['context'] = self._context
jdata['certificate_list'] = [{
'cert': e['cert'],
'extensions': [x.to_json() for x in e['extensions']],
} for e in self._cert_entries]
return jdata
def _enxtry_text(self, e, indent: int = 0):
ind = ' ' * (indent + 2)
return f'{ind} cert: {e["cert"]}\n'\
f'{ind} extensions: \n' + '\n'.join(
[x.to_text(indent=indent + 4) for x in e['extensions']])
def to_text(self, indent: int = 0):
ind = ' ' * (indent + 2)
return super().to_text(indent=indent) + '\n'\
f'{ind} context: {self._context}\n'\
f'{ind} certificate_list: \n' + '\n'.join(
[self._enxtry_text(e, indent+4) for e in self._cert_entries])
class SessionTicket(HSRecord):
def __init__(self, hsid: int, name: str, data):
super().__init__(hsid=hsid, name=name, data=data)
d = data
d, self._lifetime = _get_int(d, 4)
d, self._age = _get_int(d, 4)
d, self._nonce = _get_len_field(d, 1)
d, self._ticket = _get_len_field(d, 2)
d, edata = _get_len_field(d, 2)
self._extensions = TlsExtensions.from_data(hsid, edata)
def to_json(self):
jdata = super().to_json()
jdata['lifetime'] = self._lifetime
jdata['age'] = self._age
jdata['nonce'] = binascii.hexlify(self._nonce).decode()
jdata['ticket'] = binascii.hexlify(self._ticket).decode()
jdata['extensions'] = [ext.to_json() for ext in self._extensions]
return jdata
class HSIterator(Iterator):
def __init__(self, recs):
self._recs = recs
self._index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self._recs[self._index]
except IndexError:
raise StopIteration
self._index += 1
return result
class HandShake:
REC_TYPES = [
(1, 'ClientHello', ClientHello),
(2, 'ServerHello', ServerHello),
(3, 'HelloVerifyRequest', HSRecord),
(4, 'SessionTicket', SessionTicket),
(5, 'EndOfEarlyData', HSRecord),
(6, 'HelloRetryRequest', ServerHello),
(8, 'EncryptedExtensions', EncryptedExtensions),
(11, 'Certificate', Certificate),
(12, 'ServerKeyExchange ', HSRecord),
(13, 'CertificateRequest', CertificateRequest),
(14, 'ServerHelloDone', HSRecord),
(15, 'CertificateVerify', HSRecord),
(16, 'ClientKeyExchange', HSRecord),
(20, 'Finished', HSRecord),
(22, 'CertificateStatus', HSRecord),
(24, 'KeyUpdate', HSRecord),
(25, 'CompressedCertificate', HSRecord),
]
RT_NAME_BY_ID = {}
RT_CLS_BY_ID = {}
@classmethod
def _parse_rec(cls, data):
d, hsid = _get_int(data, 1)
if hsid not in cls.RT_CLS_BY_ID:
raise ParseError(f'unknown type {hsid}')
d, rec_len = _get_int(d, 3)
if rec_len > len(d):
# incomplete, need more data
return data, None
d, rec_data = _get_field(d, rec_len)
if hsid in cls.RT_CLS_BY_ID:
name = cls.RT_NAME_BY_ID[hsid]
rcls = cls.RT_CLS_BY_ID[hsid]
else:
name = f'CryptoRecord(0x{hsid:0x})'
rcls = HSRecord
return d, rcls(hsid=hsid, name=name, data=rec_data)
@classmethod
def _parse(cls, source, strict=False, verbose: int = 0):
d = b''
hsid = 0
hsrecs = []
if verbose > 0:
log.debug(f'scanning for handshake records')
blocks = [d for d in source]
while len(blocks) > 0:
try:
total_data = b''.join(blocks)
remain, r = cls._parse_rec(total_data)
if r is None:
# if we could not recognize a record, skip the first
# data block and try again
blocks = blocks[1:]
continue
hsrecs.append(r)
cons_len = len(total_data) - len(remain)
while cons_len > 0 and len(blocks) > 0:
if cons_len >= len(blocks[0]):
cons_len -= len(blocks[0])
blocks = blocks[1:]
else:
blocks[0] = blocks[0][cons_len:]
cons_len = 0
if verbose > 2:
log.debug(f'added record: {r.to_text()}')
except ParseError as err:
# if we could not recognize a record, skip the first
# data block and try again
blocks = blocks[1:]
if len(blocks) > 0 and strict:
raise Exception(f'possibly incomplete handshake record '
f'id={hsid}, from raw={blocks}\n')
return hsrecs
@classmethod
def init(cls):
for (hsid, name, rcls) in cls.REC_TYPES:
cls.RT_NAME_BY_ID[hsid] = name
cls.RT_CLS_BY_ID[hsid] = rcls
def __init__(self, source: Iterable[bytes], strict: bool = False,
verbose: int = 0):
self._source = source
self._strict = strict
self._verbose = verbose
def __iter__(self):
return HSIterator(recs=self._parse(self._source, strict=self._strict,
verbose=self._verbose))
HandShake.init()

View File

@ -0,0 +1,30 @@
import pytest
from .ngtcp2test import ExampleClient
from .ngtcp2test import ExampleServer
from .ngtcp2test import Env
@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0,
reason="no crypto lib examples configured")
class TestHandshake:
@pytest.fixture(scope='class', params=Env.get_crypto_libs())
def server(self, env, request) -> ExampleServer:
s = ExampleServer(env=env, crypto_lib=request.param)
assert s.exists(), f'server not found: {s.path}'
assert s.start()
yield s
s.stop()
@pytest.fixture(scope='function', params=Env.get_crypto_libs())
def client(self, env, request) -> ExampleClient:
client = ExampleClient(env=env, crypto_lib=request.param)
assert client.exists()
yield client
def test_01_01_get(self, env: Env, server, client):
# run simple GET, no sessions, needs to give full handshake
cr = client.http_get(server, url=f'https://{env.example_domain}/')
assert cr.returncode == 0
cr.assert_non_resume_handshake()

View File

@ -0,0 +1,46 @@
import pytest
from .ngtcp2test import ExampleClient
from .ngtcp2test import ExampleServer
from .ngtcp2test import Env
@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0,
reason="no crypto lib examples configured")
class TestResume:
@pytest.fixture(scope='class', params=Env.get_crypto_libs())
def server(self, env, request) -> ExampleServer:
s = ExampleServer(env=env, crypto_lib=request.param)
assert s.exists(), f'server not found: {s.path}'
assert s.start()
yield s
s.stop()
@pytest.fixture(scope='function', params=Env.get_crypto_libs())
def client(self, env, request) -> ExampleClient:
client = ExampleClient(env=env, crypto_lib=request.param)
assert client.exists()
yield client
def test_02_01(self, env: Env, server, client):
# run GET with sessions but no early data, cleared first, then reused
client.clear_session()
cr = client.http_get(server, url=f'https://{env.example_domain}/',
use_session=True,
extra_args=['--disable-early-data'])
assert cr.returncode == 0
cr.assert_non_resume_handshake()
# Now do this again and we expect a resumption, meaning no certificate
cr = client.http_get(server, url=f'https://{env.example_domain}/',
use_session=True,
extra_args=['--disable-early-data'])
assert cr.returncode == 0
cr.assert_resume_handshake()
# restart the server, do it again
server.restart()
cr = client.http_get(server, url=f'https://{env.example_domain}/',
use_session=True,
extra_args=['--disable-early-data'])
assert cr.returncode == 0
cr.assert_non_resume_handshake()

View File

@ -0,0 +1,56 @@
import pytest
from .ngtcp2test import ExampleClient
from .ngtcp2test import ExampleServer
from .ngtcp2test import Env
@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0,
reason="no crypto lib examples configured")
class TestEarlyData:
@pytest.fixture(scope='class', params=Env.get_crypto_libs())
def server(self, env, request) -> ExampleServer:
s = ExampleServer(env=env, crypto_lib=request.param)
assert s.exists(), f'server not found: {s.path}'
assert s.start()
yield s
s.stop()
@pytest.fixture(scope='function', params=Env.get_crypto_libs())
def client(self, env, request) -> ExampleClient:
client = ExampleClient(env=env, crypto_lib=request.param)
assert client.exists()
yield client
def test_03_01(self, env: Env, server, client):
# run GET with sessions, cleared first, without a session, early
# data will not even be attempted
client.clear_session()
edata = 'This is the early data. It is not much.'
cr = client.http_get(server, url=f'https://{env.example_domain}/',
use_session=True, data=edata)
assert cr.returncode == 0
cr.assert_non_resume_handshake()
# resume session, early data is sent and accepted
cr = client.http_get(server, url=f'https://{env.example_domain}/',
use_session=True, data=edata)
assert cr.returncode == 0
cr.assert_resume_handshake()
assert not cr.early_data_rejected
# restart the server, resume, early data is attempted but will not work
server.restart()
cr = client.http_get(server, url=f'https://{env.example_domain}/',
use_session=True, data=edata)
assert cr.returncode == 0
assert cr.early_data_rejected
cr.assert_non_resume_handshake()
# restart again, sent data, but not as early data
server.restart()
cr = client.http_get(server, url=f'https://{env.example_domain}/',
use_session=True, data=edata,
extra_args=['--disable-early-data'])
assert cr.returncode == 0
# we see no rejection, since it was not used
assert not cr.early_data_rejected
cr.assert_non_resume_handshake()

View File

@ -0,0 +1,57 @@
import pytest
from .ngtcp2test import ExampleClient
from .ngtcp2test import ExampleServer
from .ngtcp2test import Env
@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0,
reason="no crypto lib examples configured")
class TestClientCert:
@pytest.fixture(scope='class', params=Env.get_crypto_libs())
def server(self, env, request) -> ExampleServer:
s = ExampleServer(env=env, crypto_lib=request.param,
verify_client=True)
assert s.exists(), f'server not found: {s.path}'
assert s.start()
yield s
s.stop()
@pytest.fixture(scope='function', params=Env.get_crypto_libs())
def client(self, env, request) -> ExampleClient:
client = ExampleClient(env=env, crypto_lib=request.param)
assert client.exists()
yield client
def test_04_01(self, env: Env, server, client):
# run GET with a server requesting a cert, client has none to offer
cr = client.http_get(server, url=f'https://{env.example_domain}/')
assert cr.returncode == 0
cr.assert_verify_null_handshake()
creqs = [r for r in cr.handshake if r.hsid == 13] # CertificateRequest
assert len(creqs) == 1
creq = creqs[0].to_json()
certs = [r for r in cr.server.handshake if r.hsid == 11] # Certificate
assert len(certs) == 1
crec = certs[0].to_json()
assert len(crec['certificate_list']) == 0
assert creq['context'] == crec['context']
# TODO: check that GET had no answer
def test_04_02(self, env: Env, server, client):
# run GET with a server requesting a cert, client has cert to offer
credentials = env.ca.get_first("clientsX")
cr = client.http_get(server, url=f'https://{env.example_domain}/',
credentials=credentials)
assert cr.returncode == 0
cr.assert_verify_cert_handshake()
creqs = [r for r in cr.handshake if r.hsid == 13] # CertificateRequest
assert len(creqs) == 1
creq = creqs[0].to_json()
certs = [r for r in cr.server.handshake if r.hsid == 11] # Certificate
assert len(certs) == 1
crec = certs[0].to_json()
assert len(crec['certificate_list']) == 1
assert creq['context'] == crec['context']
# TODO: check that GET indeed gave a response

View File

@ -0,0 +1,46 @@
import sys
import pytest
from .ngtcp2test import ExampleClient
from .ngtcp2test import ExampleServer
from .ngtcp2test import Env
@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0,
reason="no crypto lib examples configured")
class TestCiphers:
@pytest.fixture(scope='class', params=Env.get_crypto_libs())
def server(self, env, request) -> ExampleServer:
s = ExampleServer(env=env, crypto_lib=request.param)
assert s.exists(), f'server not found: {s.path}'
assert s.start()
yield s
s.stop()
@pytest.fixture(scope='function',
params=Env.get_crypto_libs(configurable_ciphers=True))
def client(self, env, request) -> ExampleClient:
client = ExampleClient(env=env, crypto_lib=request.param)
assert client.exists()
yield client
@pytest.mark.parametrize('cipher', [
'TLS_AES_128_GCM_SHA256',
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_CCM_SHA256',
])
def test_05_01_get(self, env: Env, server, client, cipher):
if not client.uses_cipher_config:
pytest.skip(f'client {client.crypto_lib} ignores cipher config\n')
# run simple GET, no sessions, needs to give full handshake
if not client.supports_cipher(cipher):
pytest.skip(f'client {client.crypto_lib} does not support {cipher}\n')
if not server.supports_cipher(cipher):
pytest.skip(f'server {server.crypto_lib} does not support {cipher}\n')
cr = client.http_get(server, url=f'https://{env.example_domain}/',
ciphers=cipher)
assert cr.returncode == 0
cr.assert_non_resume_handshake()

View File

@ -0,0 +1,56 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_CLIENT_CONTEXT_H
#define TLS_CLIENT_CONTEXT_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#ifdef WITH_EXAMPLE_QUICTLS
# include "tls_client_context_quictls.h"
#endif // defined(WITH_EXAMPLE_QUICTLS)
#ifdef WITH_EXAMPLE_GNUTLS
# include "tls_client_context_gnutls.h"
#endif // defined(WITH_EXAMPLE_GNUTLS)
#ifdef WITH_EXAMPLE_BORINGSSL
# include "tls_client_context_boringssl.h"
#endif // defined(WITH_EXAMPLE_BORINGSSL)
#ifdef WITH_EXAMPLE_PICOTLS
# include "tls_client_context_picotls.h"
#endif // defined(WITH_EXAMPLE_PICOTLS)
#ifdef WITH_EXAMPLE_WOLFSSL
# include "tls_client_context_wolfssl.h"
#endif // defined(WITH_EXAMPLE_WOLFSSL)
#ifdef WITH_EXAMPLE_OSSL
# include "tls_client_context_ossl.h"
#endif // defined(WITH_EXAMPLE_OSSL)
#endif // !defined(TLS_CLIENT_CONTEXT_H)

View File

@ -0,0 +1,142 @@
/*
* ngtcp2
*
* Copyright (c) 2021 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_client_context_boringssl.h"
#include <cstring>
#include <iostream>
#include <fstream>
#include <ngtcp2/ngtcp2_crypto_boringssl.h>
#include <openssl/err.h>
#include "client_base.h"
#include "template.h"
#include "tls_shared_boringssl.h"
extern Config config;
TLSClientContext::TLSClientContext() : ssl_ctx_{nullptr} {}
TLSClientContext::~TLSClientContext() {
if (ssl_ctx_) {
SSL_CTX_free(ssl_ctx_);
}
}
SSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; }
namespace {
int new_session_cb(SSL *ssl, SSL_SESSION *session) {
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto c = static_cast<ClientBase *>(conn_ref->user_data);
c->ticket_received();
auto f = BIO_new_file(config.session_file, "w");
if (f == nullptr) {
std::cerr << "Could not write TLS session in " << config.session_file
<< std::endl;
return 0;
}
if (!PEM_write_bio_SSL_SESSION(f, session)) {
std::cerr << "Unable to write TLS session to file" << std::endl;
}
BIO_free(f);
return 0;
}
} // namespace
int TLSClientContext::init(const char *private_key_file,
const char *cert_file) {
ssl_ctx_ = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx_) {
std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
if (ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx_) != 0) {
std::cerr << "ngtcp2_crypto_boringssl_configure_client_context failed"
<< std::endl;
return -1;
}
SSL_CTX_set_default_verify_paths(ssl_ctx_);
if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) {
std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
return -1;
}
if (private_key_file && cert_file) {
if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
SSL_FILETYPE_PEM) != 1) {
std::cerr << "SSL_CTX_use_PrivateKey_file: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) {
std::cerr << "SSL_CTX_use_certificate_chain_file: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
}
if (config.session_file) {
SSL_CTX_set_session_cache_mode(ssl_ctx_, SSL_SESS_CACHE_CLIENT |
SSL_SESS_CACHE_NO_INTERNAL);
SSL_CTX_sess_set_new_cb(ssl_ctx_, new_session_cb);
}
#ifdef HAVE_LIBBROTLI
if (!SSL_CTX_add_cert_compression_alg(
ssl_ctx_, ngtcp2::tls::CERTIFICATE_COMPRESSION_ALGO_BROTLI,
ngtcp2::tls::cert_compress, ngtcp2::tls::cert_decompress)) {
std::cerr << "SSL_CTX_add_cert_compression_alg failed" << std::endl;
return -1;
}
#endif // defined(HAVE_LIBBROTLI)
return 0;
}
extern std::ofstream keylog_file;
namespace {
void keylog_callback(const SSL *ssl, const char *line) {
keylog_file.write(line, static_cast<std::streamsize>(strlen(line)));
keylog_file.put('\n');
keylog_file.flush();
}
} // namespace
void TLSClientContext::enable_keylog() {
SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
}

View File

@ -0,0 +1,49 @@
/*
* ngtcp2
*
* Copyright (c) 2021 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_CLIENT_CONTEXT_BORINGSSL_H
#define TLS_CLIENT_CONTEXT_BORINGSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <openssl/ssl.h>
class TLSClientContext {
public:
TLSClientContext();
~TLSClientContext();
int init(const char *private_key_file, const char *cert_file);
SSL_CTX *get_native_handle() const;
void enable_keylog();
private:
SSL_CTX *ssl_ctx_;
};
#endif // !defined(TLS_CLIENT_CONTEXT_BORINGSSL_H)

View File

@ -0,0 +1,149 @@
/*
* ngtcp2
*
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_client_context_ossl.h"
#include <cstring>
#include <cassert>
#include <iostream>
#include <fstream>
#include <limits>
#include <ngtcp2/ngtcp2_crypto_ossl.h>
#include <openssl/err.h>
#include "client_base.h"
#include "template.h"
namespace {
auto _ = []() {
if (ngtcp2_crypto_ossl_init() != 0) {
assert(0);
abort();
}
return 0;
}();
} // namespace
extern Config config;
TLSClientContext::TLSClientContext() : ssl_ctx_{nullptr} {}
TLSClientContext::~TLSClientContext() {
if (ssl_ctx_) {
SSL_CTX_free(ssl_ctx_);
}
}
SSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; }
namespace {
int new_session_cb(SSL *ssl, SSL_SESSION *session) {
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto c = static_cast<ClientBase *>(conn_ref->user_data);
c->ticket_received();
if (SSL_SESSION_get_max_early_data(session) !=
std::numeric_limits<uint32_t>::max()) {
std::cerr << "max_early_data_size is not 0xffffffff" << std::endl;
}
auto f = BIO_new_file(config.session_file, "w");
if (f == nullptr) {
std::cerr << "Could not write TLS session in " << config.session_file
<< std::endl;
return 0;
}
if (!PEM_write_bio_SSL_SESSION(f, session)) {
std::cerr << "Unable to write TLS session to file" << std::endl;
}
BIO_free(f);
return 0;
}
} // namespace
int TLSClientContext::init(const char *private_key_file,
const char *cert_file) {
ssl_ctx_ = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx_) {
std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
SSL_CTX_set_default_verify_paths(ssl_ctx_);
if (SSL_CTX_set_ciphersuites(ssl_ctx_, config.ciphers) != 1) {
std::cerr << "SSL_CTX_set_ciphersuites: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) {
std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
return -1;
}
if (private_key_file && cert_file) {
if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
SSL_FILETYPE_PEM) != 1) {
std::cerr << "SSL_CTX_use_PrivateKey_file: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) {
std::cerr << "SSL_CTX_use_certificate_chain_file: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
}
if (config.session_file) {
SSL_CTX_set_session_cache_mode(ssl_ctx_, SSL_SESS_CACHE_CLIENT |
SSL_SESS_CACHE_NO_INTERNAL);
SSL_CTX_sess_set_new_cb(ssl_ctx_, new_session_cb);
}
return 0;
}
extern std::ofstream keylog_file;
namespace {
void keylog_callback(const SSL *ssl, const char *line) {
keylog_file.write(line, static_cast<std::streamsize>(strlen(line)));
keylog_file.put('\n');
keylog_file.flush();
}
} // namespace
void TLSClientContext::enable_keylog() {
SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
}

View File

@ -0,0 +1,49 @@
/*
* ngtcp2
*
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_CLIENT_CONTEXT_OSSL_H
#define TLS_CLIENT_CONTEXT_OSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <openssl/ssl.h>
class TLSClientContext {
public:
TLSClientContext();
~TLSClientContext();
int init(const char *private_key_file, const char *cert_file);
SSL_CTX *get_native_handle() const;
void enable_keylog();
private:
SSL_CTX *ssl_ctx_;
};
#endif // !defined(TLS_CLIENT_CONTEXT_OSSL_H)

View File

@ -0,0 +1,171 @@
/*
* ngtcp2
*
* Copyright (c) 2022 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_client_context_picotls.h"
#include <iostream>
#include <ngtcp2/ngtcp2_crypto_picotls.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include "client_base.h"
#include "tls_shared_picotls.h"
#include "template.h"
extern Config config;
namespace {
int save_ticket_cb(ptls_save_ticket_t *self, ptls_t *ptls, ptls_iovec_t input) {
auto conn_ref =
static_cast<ngtcp2_crypto_conn_ref *>(*ptls_get_data_ptr(ptls));
auto c = static_cast<ClientBase *>(conn_ref->user_data);
c->ticket_received();
auto f = BIO_new_file(config.session_file, "w");
if (f == nullptr) {
std::cerr << "Could not write TLS session in " << config.session_file
<< std::endl;
return 0;
}
if (!PEM_write_bio(f, "PICOTLS SESSION PARAMETERS", "", input.base,
static_cast<long>(input.len))) {
std::cerr << "Unable to write TLS session to file" << std::endl;
}
BIO_free(f);
return 0;
}
ptls_save_ticket_t save_ticket = {save_ticket_cb};
} // namespace
namespace {
ptls_key_exchange_algorithm_t *key_exchanges[] = {
#if PTLS_OPENSSL_HAVE_X25519
&ptls_openssl_x25519,
#endif // PTLS_OPENSSL_X25519
&ptls_openssl_secp256r1,
&ptls_openssl_secp384r1,
&ptls_openssl_secp521r1,
#if PTLS_OPENSSL_HAVE_X25519MLKEM768
&ptls_openssl_x25519mlkem768,
#endif // PTLS_OPENSSL_HAVE_X25519MLKEM768
nullptr,
};
} // namespace
namespace {
ptls_cipher_suite_t *cipher_suites[] = {
&ptls_openssl_aes128gcmsha256,
&ptls_openssl_aes256gcmsha384,
#if PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
&ptls_openssl_chacha20poly1305sha256,
#endif // PTLS_OPENSSL_CHACHA20POLY1305SHA256
nullptr,
};
} // namespace
TLSClientContext::TLSClientContext()
: ctx_{
.random_bytes = ptls_openssl_random_bytes,
.get_time = &ptls_get_time,
.key_exchanges = key_exchanges,
.cipher_suites = cipher_suites,
.require_dhe_on_psk = 1,
}, sign_cert_{} {}
TLSClientContext::~TLSClientContext() {
if (sign_cert_.key) {
ptls_openssl_dispose_sign_certificate(&sign_cert_);
}
for (size_t i = 0; i < ctx_.certificates.count; ++i) {
free(ctx_.certificates.list[i].base);
}
free(ctx_.certificates.list);
}
ptls_context_t *TLSClientContext::get_native_handle() { return &ctx_; }
int TLSClientContext::init(const char *private_key_file,
const char *cert_file) {
if (ngtcp2_crypto_picotls_configure_client_context(&ctx_) != 0) {
std::cerr << "ngtcp2_crypto_picotls_configure_client_context failed"
<< std::endl;
return -1;
}
if (config.session_file) {
ctx_.save_ticket = &save_ticket;
}
if (private_key_file && cert_file) {
if (ptls_load_certificates(&ctx_, cert_file) != 0) {
std::cerr << "ptls_load_certificates failed" << std::endl;
return -1;
}
if (load_private_key(private_key_file) != 0) {
return -1;
}
}
return 0;
}
int TLSClientContext::load_private_key(const char *private_key_file) {
auto fp = fopen(private_key_file, "rb");
if (fp == nullptr) {
std::cerr << "Could not open private key file " << private_key_file << ": "
<< strerror(errno) << std::endl;
return -1;
}
auto fp_d = defer(fclose, fp);
auto pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr);
if (pkey == nullptr) {
std::cerr << "Could not read private key file " << private_key_file
<< std::endl;
return -1;
}
auto pkey_d = defer(EVP_PKEY_free, pkey);
if (ptls_openssl_init_sign_certificate(&sign_cert_, pkey) != 0) {
std::cerr << "ptls_openssl_init_sign_certificate failed" << std::endl;
return -1;
}
ctx_.sign_certificate = &sign_cert_.super;
return 0;
}
void TLSClientContext::enable_keylog() { ctx_.log_event = &log_event; }

View File

@ -0,0 +1,53 @@
/*
* ngtcp2
*
* Copyright (c) 2022 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_CLIENT_CONTEXT_PICOTLS_H
#define TLS_CLIENT_CONTEXT_PICOTLS_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <picotls.h>
#include <picotls/openssl.h>
class TLSClientContext {
public:
TLSClientContext();
~TLSClientContext();
int init(const char *private_key_file, const char *cert_file);
ptls_context_t *get_native_handle();
void enable_keylog();
private:
int load_private_key(const char *private_key_file);
ptls_context_t ctx_;
ptls_openssl_sign_certificate_t sign_cert_;
};
#endif // !defined(TLS_CLIENT_CONTEXT_PICOTLS_H)

View File

@ -0,0 +1,155 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_client_context_quictls.h"
#include <cstring>
#include <cassert>
#include <iostream>
#include <fstream>
#include <limits>
#include <ngtcp2/ngtcp2_crypto_quictls.h>
#include <openssl/err.h>
#include "client_base.h"
#include "template.h"
namespace {
auto _ = []() {
if (ngtcp2_crypto_quictls_init() != 0) {
assert(0);
abort();
}
return 0;
}();
} // namespace
extern Config config;
TLSClientContext::TLSClientContext() : ssl_ctx_{nullptr} {}
TLSClientContext::~TLSClientContext() {
if (ssl_ctx_) {
SSL_CTX_free(ssl_ctx_);
}
}
SSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; }
namespace {
int new_session_cb(SSL *ssl, SSL_SESSION *session) {
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto c = static_cast<ClientBase *>(conn_ref->user_data);
c->ticket_received();
if (SSL_SESSION_get_max_early_data(session) !=
std::numeric_limits<uint32_t>::max()) {
std::cerr << "max_early_data_size is not 0xffffffff" << std::endl;
}
auto f = BIO_new_file(config.session_file, "w");
if (f == nullptr) {
std::cerr << "Could not write TLS session in " << config.session_file
<< std::endl;
return 0;
}
if (!PEM_write_bio_SSL_SESSION(f, session)) {
std::cerr << "Unable to write TLS session to file" << std::endl;
}
BIO_free(f);
return 0;
}
} // namespace
int TLSClientContext::init(const char *private_key_file,
const char *cert_file) {
ssl_ctx_ = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx_) {
std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
if (ngtcp2_crypto_quictls_configure_client_context(ssl_ctx_) != 0) {
std::cerr << "ngtcp2_crypto_quictls_configure_client_context failed"
<< std::endl;
return -1;
}
SSL_CTX_set_default_verify_paths(ssl_ctx_);
if (SSL_CTX_set_ciphersuites(ssl_ctx_, config.ciphers) != 1) {
std::cerr << "SSL_CTX_set_ciphersuites: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) {
std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
return -1;
}
if (private_key_file && cert_file) {
if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
SSL_FILETYPE_PEM) != 1) {
std::cerr << "SSL_CTX_use_PrivateKey_file: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) {
std::cerr << "SSL_CTX_use_certificate_chain_file: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
}
if (config.session_file) {
SSL_CTX_set_session_cache_mode(ssl_ctx_, SSL_SESS_CACHE_CLIENT |
SSL_SESS_CACHE_NO_INTERNAL);
SSL_CTX_sess_set_new_cb(ssl_ctx_, new_session_cb);
}
return 0;
}
extern std::ofstream keylog_file;
namespace {
void keylog_callback(const SSL *ssl, const char *line) {
keylog_file.write(line, static_cast<std::streamsize>(strlen(line)));
keylog_file.put('\n');
keylog_file.flush();
}
} // namespace
void TLSClientContext::enable_keylog() {
SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
}

View File

@ -0,0 +1,49 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_CLIENT_CONTEXT_QUICTLS_H
#define TLS_CLIENT_CONTEXT_QUICTLS_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <openssl/ssl.h>
class TLSClientContext {
public:
TLSClientContext();
~TLSClientContext();
int init(const char *private_key_file, const char *cert_file);
SSL_CTX *get_native_handle() const;
void enable_keylog();
private:
SSL_CTX *ssl_ctx_;
};
#endif // !defined(TLS_CLIENT_CONTEXT_QUICTLS_H)

View File

@ -0,0 +1,184 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_client_context_wolfssl.h"
#include <cstring>
#include <iostream>
#include <fstream>
#include <limits>
#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#include "client_base.h"
#include "template.h"
extern Config config;
TLSClientContext::TLSClientContext() : ssl_ctx_{nullptr} {}
TLSClientContext::~TLSClientContext() {
if (ssl_ctx_) {
wolfSSL_CTX_free(ssl_ctx_);
}
}
WOLFSSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; }
namespace {
int new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) {
std::cerr << "new_session_cb called" << std::endl;
auto conn_ref =
static_cast<ngtcp2_crypto_conn_ref *>(wolfSSL_get_app_data(ssl));
auto c = static_cast<ClientBase *>(conn_ref->user_data);
c->ticket_received();
#ifdef HAVE_SESSION_TICKET
if (wolfSSL_SESSION_get_max_early_data(session) !=
std::numeric_limits<uint32_t>::max()) {
std::cerr << "max_early_data_size is not 0xffffffff" << std::endl;
}
unsigned char sbuffer[16 * 1024], *data;
auto sz = wolfSSL_i2d_SSL_SESSION(session, nullptr);
if (sz <= 0) {
std::cerr << "Could not export TLS session in " << config.session_file
<< std::endl;
return 0;
}
if (static_cast<size_t>(sz) > sizeof(sbuffer)) {
std::cerr << "Exported TLS session too large" << std::endl;
return 0;
}
data = sbuffer;
sz = wolfSSL_i2d_SSL_SESSION(session, &data);
auto f = wolfSSL_BIO_new_file(config.session_file, "w");
if (f == nullptr) {
std::cerr << "Could not write TLS session in " << config.session_file
<< std::endl;
return 0;
}
auto f_d = defer(wolfSSL_BIO_free, f);
if (!wolfSSL_PEM_write_bio(f, "WOLFSSL SESSION PARAMETERS", "", sbuffer,
sz)) {
std::cerr << "Unable to write TLS session to file" << std::endl;
return 0;
}
std::cerr << "new_session_cb: wrote " << sz << " of session data"
<< std::endl;
#else // !defined(HAVE_SESSION_TICKET)
std::cerr << "TLS session tickets not enabled in wolfSSL " << std::endl;
#endif // !defined(HAVE_SESSION_TICKET)
return 0;
}
} // namespace
int TLSClientContext::init(const char *private_key_file,
const char *cert_file) {
ssl_ctx_ = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
if (!ssl_ctx_) {
std::cerr << "wolfSSL_CTX_new: "
<< wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
if (ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx_) != 0) {
std::cerr << "ngtcp2_crypto_wolfssl_configure_client_context failed"
<< std::endl;
return -1;
}
if (wolfSSL_CTX_set_default_verify_paths(ssl_ctx_) ==
WOLFSSL_NOT_IMPLEMENTED) {
/* hmm, not verifying the server cert for now */
wolfSSL_CTX_set_verify(ssl_ctx_, WOLFSSL_VERIFY_NONE, 0);
}
if (wolfSSL_CTX_set_cipher_list(ssl_ctx_, config.ciphers) !=
WOLFSSL_SUCCESS) {
std::cerr << "wolfSSL_CTX_set_cipher_list: "
<< wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
if (wolfSSL_CTX_set1_groups_list(
ssl_ctx_, const_cast<char *>(config.groups)) != WOLFSSL_SUCCESS) {
std::cerr << "wolfSSL_CTX_set1_groups_list(" << config.groups << ") failed"
<< std::endl;
return -1;
}
if (private_key_file && cert_file) {
if (wolfSSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
SSL_FILETYPE_PEM) != WOLFSSL_SUCCESS) {
std::cerr << "wolfSSL_CTX_use_PrivateKey_file: "
<< wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
if (wolfSSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) !=
WOLFSSL_SUCCESS) {
std::cerr << "wolfSSL_CTX_use_certificate_chain_file: "
<< wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
}
if (config.session_file) {
wolfSSL_CTX_UseSessionTicket(ssl_ctx_);
wolfSSL_CTX_sess_set_new_cb(ssl_ctx_, new_session_cb);
}
return 0;
}
extern std::ofstream keylog_file;
#ifdef HAVE_SECRET_CALLBACK
namespace {
void keylog_callback(const WOLFSSL *ssl, const char *line) {
keylog_file.write(line, static_cast<std::streamsize>(strlen(line)));
keylog_file.put('\n');
keylog_file.flush();
}
} // namespace
#endif // defined(HAVE_SECRET_CALLBACK)
void TLSClientContext::enable_keylog() {
#ifdef HAVE_SECRET_CALLBACK
wolfSSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
#endif // defined(HAVE_SECRET_CALLBACK)
}

View File

@ -0,0 +1,51 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_CLIENT_CONTEXT_WOLFSSL_H
#define TLS_CLIENT_CONTEXT_WOLFSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#include <wolfssl/quic.h>
class TLSClientContext {
public:
TLSClientContext();
~TLSClientContext();
int init(const char *private_key_file, const char *cert_file);
WOLFSSL_CTX *get_native_handle() const;
void enable_keylog();
private:
WOLFSSL_CTX *ssl_ctx_;
};
#endif // !defined(TLS_CLIENT_CONTEXT_WOLFSSL_H)

View File

@ -0,0 +1,56 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_CLIENT_SESSION_H
#define TLS_CLIENT_SESSION_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#ifdef WITH_EXAMPLE_QUICTLS
# include "tls_client_session_quictls.h"
#endif // defined(WITH_EXAMPLE_QUICTLS)
#ifdef WITH_EXAMPLE_GNUTLS
# include "tls_client_session_gnutls.h"
#endif // defined(WITH_EXAMPLE_GNUTLS)
#ifdef WITH_EXAMPLE_BORINGSSL
# include "tls_client_session_boringssl.h"
#endif // defined(WITH_EXAMPLE_BORINGSSL)
#ifdef WITH_EXAMPLE_PICOTLS
# include "tls_client_session_picotls.h"
#endif // defined(WITH_EXAMPLE_PICOTLS)
#ifdef WITH_EXAMPLE_WOLFSSL
# include "tls_client_session_wolfssl.h"
#endif // defined(WITH_EXAMPLE_WOLFSSL)
#ifdef WITH_EXAMPLE_OSSL
# include "tls_client_session_ossl.h"
#endif // defined(WITH_EXAMPLE_OSSL)
#endif // !defined(TLS_CLIENT_SESSION_H)

View File

@ -0,0 +1,147 @@
/*
* ngtcp2
*
* Copyright (c) 2021 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_client_session_boringssl.h"
#include <cassert>
#include <iostream>
#include <fstream>
#include "tls_client_context_boringssl.h"
#include "client_base.h"
#include "template.h"
#include "util.h"
TLSClientSession::TLSClientSession() {}
TLSClientSession::~TLSClientSession() {}
extern Config config;
int TLSClientSession::init(bool &early_data_enabled,
const TLSClientContext &tls_ctx,
const char *remote_addr, ClientBase *client,
uint32_t quic_version, AppProtocol app_proto) {
early_data_enabled = false;
auto ssl_ctx = tls_ctx.get_native_handle();
ssl_ = SSL_new(ssl_ctx);
if (!ssl_) {
std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
SSL_set_app_data(ssl_, client->conn_ref());
SSL_set_connect_state(ssl_);
switch (app_proto) {
case AppProtocol::H3:
SSL_set_alpn_protos(ssl_, H3_ALPN.data(), H3_ALPN.size());
break;
case AppProtocol::HQ:
SSL_set_alpn_protos(ssl_, HQ_ALPN.data(), HQ_ALPN.size());
break;
}
if (!config.sni.empty()) {
SSL_set_tlsext_host_name(ssl_, config.sni.data());
} else if (util::numeric_host(remote_addr)) {
// If remote host is numeric address, just send "localhost" as SNI
// for now.
SSL_set_tlsext_host_name(ssl_, "localhost");
} else {
SSL_set_tlsext_host_name(ssl_, remote_addr);
}
if (config.session_file) {
auto f = BIO_new_file(config.session_file, "r");
if (f == nullptr) {
std::cerr << "Could not read TLS session file " << config.session_file
<< std::endl;
} else {
auto session = PEM_read_bio_SSL_SESSION(f, nullptr, 0, nullptr);
BIO_free(f);
if (session == nullptr) {
std::cerr << "Could not read TLS session file " << config.session_file
<< std::endl;
} else {
if (!SSL_set_session(ssl_, session)) {
std::cerr << "Could not set session" << std::endl;
} else if (!config.disable_early_data &&
SSL_SESSION_early_data_capable(session)) {
early_data_enabled = true;
SSL_set_early_data_enabled(ssl_, 1);
}
SSL_SESSION_free(session);
}
}
}
if (!config.ech_config_list.empty() &&
SSL_set1_ech_config_list(ssl_, config.ech_config_list.data(),
config.ech_config_list.size()) != 1) {
std::cerr << "Could not set ECHConfigList: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
return 0;
}
bool TLSClientSession::get_early_data_accepted() const {
return SSL_early_data_accepted(ssl_);
}
bool TLSClientSession::get_ech_accepted() const {
return SSL_ech_accepted(ssl_);
}
int TLSClientSession::write_ech_config_list(const char *path) const {
const uint8_t *retry_configs;
size_t retry_configslen;
SSL_get0_ech_retry_configs(ssl_, &retry_configs, &retry_configslen);
if (retry_configslen == 0) {
std::cerr << "No ECH retry configs found" << std::endl;
return -1;
}
auto f = std::ofstream(path);
if (!f) {
return -1;
}
f.write(reinterpret_cast<const char *>(retry_configs),
static_cast<std::streamsize>(retry_configslen));
f.close();
if (!f) {
return -1;
}
return 0;
}

View File

@ -0,0 +1,54 @@
/*
* ngtcp2
*
* Copyright (c) 2021 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_CLIENT_SESSION_BORINGSSL_H
#define TLS_CLIENT_SESSION_BORINGSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include "tls_session_base_quictls.h"
#include "shared.h"
using namespace ngtcp2;
class TLSClientContext;
class ClientBase;
class TLSClientSession : public TLSSessionBase {
public:
TLSClientSession();
~TLSClientSession();
int init(bool &early_data_enabled, const TLSClientContext &tls_ctx,
const char *remote_addr, ClientBase *client, uint32_t quic_version,
AppProtocol app_proto);
bool get_early_data_accepted() const;
bool get_ech_accepted() const;
int write_ech_config_list(const char *path) const;
};
#endif // !defined(TLS_CLIENT_SESSION_BORINGSSL_H)

View File

@ -0,0 +1,121 @@
/*
* ngtcp2
*
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_client_session_ossl.h"
#include <cassert>
#include <iostream>
#include <openssl/err.h>
#include "tls_client_context_ossl.h"
#include "client_base.h"
#include "template.h"
#include "util.h"
TLSClientSession::TLSClientSession() {}
TLSClientSession::~TLSClientSession() {}
extern Config config;
int TLSClientSession::init(bool &early_data_enabled,
const TLSClientContext &tls_ctx,
const char *remote_addr, ClientBase *client,
uint32_t quic_version, AppProtocol app_proto) {
early_data_enabled = false;
auto ssl_ctx = tls_ctx.get_native_handle();
auto ssl = SSL_new(ssl_ctx);
if (!ssl) {
std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
ngtcp2_crypto_ossl_ctx_set_ssl(ossl_ctx_, ssl);
if (ngtcp2_crypto_ossl_configure_client_session(ssl) != 0) {
std::cerr << "ngtcp2_crypto_ossl_configure_client_session failed"
<< std::endl;
return -1;
}
SSL_set_app_data(ssl, client->conn_ref());
SSL_set_connect_state(ssl);
switch (app_proto) {
case AppProtocol::H3:
SSL_set_alpn_protos(ssl, H3_ALPN.data(), H3_ALPN.size());
break;
case AppProtocol::HQ:
SSL_set_alpn_protos(ssl, HQ_ALPN.data(), HQ_ALPN.size());
break;
}
if (!config.sni.empty()) {
SSL_set_tlsext_host_name(ssl, config.sni.data());
} else if (util::numeric_host(remote_addr)) {
// If remote host is numeric address, just send "localhost" as SNI
// for now.
SSL_set_tlsext_host_name(ssl, "localhost");
} else {
SSL_set_tlsext_host_name(ssl, remote_addr);
}
if (config.session_file) {
auto f = BIO_new_file(config.session_file, "r");
if (f == nullptr) {
std::cerr << "Could not read TLS session file " << config.session_file
<< std::endl;
} else {
auto session = PEM_read_bio_SSL_SESSION(f, nullptr, 0, nullptr);
BIO_free(f);
if (session == nullptr) {
std::cerr << "Could not read TLS session file " << config.session_file
<< std::endl;
} else {
if (!SSL_set_session(ssl, session)) {
std::cerr << "Could not set session" << std::endl;
} else if (!config.disable_early_data &&
SSL_SESSION_get_max_early_data(session)) {
early_data_enabled = true;
SSL_set_quic_tls_early_data_enabled(ssl, 1);
}
SSL_SESSION_free(session);
}
}
}
return 0;
}
bool TLSClientSession::get_early_data_accepted() const {
auto ssl = ngtcp2_crypto_ossl_ctx_get_ssl(ossl_ctx_);
// SSL_get_early_data_status works after handshake completes.
return SSL_get_early_data_status(ssl) == SSL_EARLY_DATA_ACCEPTED;
}

View File

@ -0,0 +1,54 @@
/*
* ngtcp2
*
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_CLIENT_SESSION_OSSL_H
#define TLS_CLIENT_SESSION_OSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include "tls_session_base_ossl.h"
#include "shared.h"
using namespace ngtcp2;
class TLSClientContext;
class ClientBase;
class TLSClientSession : public TLSSessionBase {
public:
TLSClientSession();
~TLSClientSession();
int init(bool &early_data_enabled, const TLSClientContext &tls_ctx,
const char *remote_addr, ClientBase *client, uint32_t quic_version,
AppProtocol app_proto);
bool get_early_data_accepted() const;
bool get_ech_accepted() const { return false; }
int write_ech_config_list(const char *path) const { return 0; }
};
#endif // !defined(TLS_CLIENT_SESSION_OSSL_H)

View File

@ -0,0 +1,169 @@
/*
* ngtcp2
*
* Copyright (c) 2022 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_client_session_picotls.h"
#include <cstring>
#include <iostream>
#include <memory>
#include <ngtcp2/ngtcp2_crypto_picotls.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <picotls.h>
#include "tls_client_context_picotls.h"
#include "client_base.h"
#include "template.h"
#include "util.h"
using namespace std::literals;
extern Config config;
TLSClientSession::TLSClientSession() {}
TLSClientSession::~TLSClientSession() {
auto &hsprops = cptls_.handshake_properties;
delete[] hsprops.client.session_ticket.base;
}
namespace {
auto negotiated_protocols_h3 = std::to_array<ptls_iovec_t>({
{
.base = const_cast<uint8_t *>(&H3_ALPN_V1[1]),
.len = H3_ALPN_V1[0],
},
});
} // namespace
namespace {
auto negotiated_protocols_hq = std::to_array<ptls_iovec_t>({
{
.base = const_cast<uint8_t *>(&HQ_ALPN_V1[1]),
.len = HQ_ALPN_V1[0],
},
});
} // namespace
int TLSClientSession::init(bool &early_data_enabled, TLSClientContext &tls_ctx,
const char *remote_addr, ClientBase *client,
uint32_t quic_version, AppProtocol app_proto) {
cptls_.ptls = ptls_client_new(tls_ctx.get_native_handle());
if (!cptls_.ptls) {
std::cerr << "ptls_client_new failed" << std::endl;
return -1;
}
*ptls_get_data_ptr(cptls_.ptls) = client->conn_ref();
auto conn = client->conn();
auto &hsprops = cptls_.handshake_properties;
hsprops.additional_extensions = new ptls_raw_extension_t[2]{
{
.type = UINT16_MAX,
},
{
.type = UINT16_MAX,
},
};
if (ngtcp2_crypto_picotls_configure_client_session(&cptls_, conn) != 0) {
std::cerr << "ngtcp2_crypto_picotls_configure_client_session failed"
<< std::endl;
return -1;
}
switch (app_proto) {
case AppProtocol::H3:
hsprops.client.negotiated_protocols.list = negotiated_protocols_h3.data();
hsprops.client.negotiated_protocols.count = negotiated_protocols_h3.size();
break;
case AppProtocol::HQ:
hsprops.client.negotiated_protocols.list = negotiated_protocols_hq.data();
hsprops.client.negotiated_protocols.count = negotiated_protocols_hq.size();
break;
}
if (util::numeric_host(remote_addr)) {
// If remote host is numeric address, just send "localhost" as SNI
// for now.
ptls_set_server_name(cptls_.ptls, "localhost", strlen("localhost"));
} else {
ptls_set_server_name(cptls_.ptls, remote_addr, strlen(remote_addr));
}
if (config.session_file) {
auto f = BIO_new_file(config.session_file, "r");
if (f == nullptr) {
std::cerr << "Could not read TLS session file " << config.session_file
<< std::endl;
} else {
auto f_d = defer(BIO_free, f);
char *name, *header;
unsigned char *data;
long datalen;
if (PEM_read_bio(f, &name, &header, &data, &datalen) != 1) {
std::cerr << "Could not read TLS session file " << config.session_file
<< std::endl;
} else {
if ("PICOTLS SESSION PARAMETERS"sv != name) {
std::cerr << "TLS session file contains unexpected name: " << name
<< std::endl;
} else {
hsprops.client.session_ticket.base =
new uint8_t[static_cast<size_t>(datalen)];
hsprops.client.session_ticket.len = static_cast<size_t>(datalen);
std::ranges::copy_n(data, datalen,
hsprops.client.session_ticket.base);
if (!config.disable_early_data) {
// No easy way to check max_early_data from ticket. We
// need to run ptls_handle_message.
early_data_enabled = true;
}
}
OPENSSL_free(name);
OPENSSL_free(header);
OPENSSL_free(data);
}
}
}
return 0;
}
bool TLSClientSession::get_early_data_accepted() const {
return cptls_.handshake_properties.client.early_data_acceptance ==
PTLS_EARLY_DATA_ACCEPTED;
}

View File

@ -0,0 +1,54 @@
/*
* ngtcp2
*
* Copyright (c) 2022 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_CLIENT_SESSION_PICOTLS_H
#define TLS_CLIENT_SESSION_PICOTLS_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include "tls_session_base_picotls.h"
#include "shared.h"
using namespace ngtcp2;
class TLSClientContext;
class ClientBase;
class TLSClientSession : public TLSSessionBase {
public:
TLSClientSession();
~TLSClientSession();
int init(bool &early_data_enabled, TLSClientContext &tls_ctx,
const char *remote_addr, ClientBase *client, uint32_t quic_version,
AppProtocol app_proto);
bool get_early_data_accepted() const;
bool get_ech_accepted() const { return false; }
int write_ech_config_list(const char *path) const { return 0; }
};
#endif // !defined(TLS_CLIENT_SESSION_PICOTLS_H)

View File

@ -0,0 +1,113 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_client_session_quictls.h"
#include <cassert>
#include <iostream>
#include <openssl/err.h>
#include "tls_client_context_quictls.h"
#include "client_base.h"
#include "template.h"
#include "util.h"
TLSClientSession::TLSClientSession() {}
TLSClientSession::~TLSClientSession() {}
extern Config config;
int TLSClientSession::init(bool &early_data_enabled,
const TLSClientContext &tls_ctx,
const char *remote_addr, ClientBase *client,
uint32_t quic_version, AppProtocol app_proto) {
early_data_enabled = false;
auto ssl_ctx = tls_ctx.get_native_handle();
ssl_ = SSL_new(ssl_ctx);
if (!ssl_) {
std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
SSL_set_app_data(ssl_, client->conn_ref());
SSL_set_connect_state(ssl_);
switch (app_proto) {
case AppProtocol::H3:
SSL_set_alpn_protos(ssl_, H3_ALPN.data(), H3_ALPN.size());
break;
case AppProtocol::HQ:
SSL_set_alpn_protos(ssl_, HQ_ALPN.data(), HQ_ALPN.size());
break;
}
if (!config.sni.empty()) {
SSL_set_tlsext_host_name(ssl_, config.sni.data());
} else if (util::numeric_host(remote_addr)) {
// If remote host is numeric address, just send "localhost" as SNI
// for now.
SSL_set_tlsext_host_name(ssl_, "localhost");
} else {
SSL_set_tlsext_host_name(ssl_, remote_addr);
}
if (config.session_file) {
auto f = BIO_new_file(config.session_file, "r");
if (f == nullptr) {
std::cerr << "Could not read TLS session file " << config.session_file
<< std::endl;
} else {
auto session = PEM_read_bio_SSL_SESSION(f, nullptr, 0, nullptr);
BIO_free(f);
if (session == nullptr) {
std::cerr << "Could not read TLS session file " << config.session_file
<< std::endl;
} else {
if (!SSL_set_session(ssl_, session)) {
std::cerr << "Could not set session" << std::endl;
}
#ifndef LIBRESSL_VERSION_NUMBER
else if (!config.disable_early_data &&
SSL_SESSION_get_max_early_data(session)) {
early_data_enabled = true;
SSL_set_quic_early_data_enabled(ssl_, 1);
}
#endif // !defined(LIBRESSL_VERSION_NUMBER)
SSL_SESSION_free(session);
}
}
}
return 0;
}
bool TLSClientSession::get_early_data_accepted() const {
// SSL_get_early_data_status works after handshake completes.
return SSL_get_early_data_status(ssl_) == SSL_EARLY_DATA_ACCEPTED;
}

View File

@ -0,0 +1,54 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_CLIENT_SESSION_QUICTLS_H
#define TLS_CLIENT_SESSION_QUICTLS_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include "tls_session_base_quictls.h"
#include "shared.h"
using namespace ngtcp2;
class TLSClientContext;
class ClientBase;
class TLSClientSession : public TLSSessionBase {
public:
TLSClientSession();
~TLSClientSession();
int init(bool &early_data_enabled, const TLSClientContext &tls_ctx,
const char *remote_addr, ClientBase *client, uint32_t quic_version,
AppProtocol app_proto);
bool get_early_data_accepted() const;
bool get_ech_accepted() const { return false; }
int write_ech_config_list(const char *path) const { return 0; }
};
#endif // !defined(TLS_CLIENT_SESSION_QUICTLS_H)

View File

@ -0,0 +1,160 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_client_session_wolfssl.h"
#include <cassert>
#include <cstring>
#include <iostream>
#include "tls_client_context_wolfssl.h"
#include "client_base.h"
#include "template.h"
#include "util.h"
using namespace std::literals;
TLSClientSession::TLSClientSession() {}
TLSClientSession::~TLSClientSession() {}
extern Config config;
namespace {
int wolfssl_session_ticket_cb(WOLFSSL *ssl, const unsigned char *ticket,
int ticketSz, void *cb_ctx) {
std::cerr << "session ticket callback invoked" << std::endl;
return 0;
}
} // namespace
int TLSClientSession::init(bool &early_data_enabled,
const TLSClientContext &tls_ctx,
const char *remote_addr, ClientBase *client,
uint32_t quic_version, AppProtocol app_proto) {
early_data_enabled = false;
auto ssl_ctx = tls_ctx.get_native_handle();
ssl_ = wolfSSL_new(ssl_ctx);
if (!ssl_) {
std::cerr << "wolfSSL_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
wolfSSL_set_app_data(ssl_, client->conn_ref());
wolfSSL_set_connect_state(ssl_);
switch (app_proto) {
case AppProtocol::H3:
wolfSSL_set_alpn_protos(ssl_, H3_ALPN.data(), H3_ALPN.size());
break;
case AppProtocol::HQ:
wolfSSL_set_alpn_protos(ssl_, HQ_ALPN.data(), HQ_ALPN.size());
break;
}
if (!config.sni.empty()) {
wolfSSL_UseSNI(ssl_, WOLFSSL_SNI_HOST_NAME, config.sni.data(),
static_cast<uint16_t>(config.sni.length()));
} else if (util::numeric_host(remote_addr)) {
// If remote host is numeric address, just send "localhost" as SNI
// for now.
wolfSSL_UseSNI(ssl_, WOLFSSL_SNI_HOST_NAME, "localhost",
sizeof("localhost") - 1);
} else {
wolfSSL_UseSNI(ssl_, WOLFSSL_SNI_HOST_NAME, remote_addr,
static_cast<uint16_t>(strlen(remote_addr)));
}
// Just use QUIC v1
wolfSSL_set_quic_transport_version(ssl_, 0x39);
if (config.session_file) {
#ifdef HAVE_SESSION_TICKET
auto f = wolfSSL_BIO_new_file(config.session_file, "r");
if (f == nullptr) {
std::cerr << "Could not open TLS session file " << config.session_file
<< std::endl;
} else {
char *name, *header;
unsigned char *data;
const unsigned char *pdata;
long datalen;
WOLFSSL_SESSION *session;
if (wolfSSL_PEM_read_bio(f, &name, &header, &data, &datalen) != 1) {
std::cerr << "Could not read TLS session file " << config.session_file
<< std::endl;
} else {
if ("WOLFSSL SESSION PARAMETERS"sv != name) {
std::cerr << "TLS session file contains unexpected name: " << name
<< std::endl;
} else {
pdata = data;
session = wolfSSL_d2i_SSL_SESSION(nullptr, &pdata, datalen);
if (session == nullptr) {
std::cerr << "Could not parse TLS session from file "
<< config.session_file << std::endl;
} else {
auto ret = wolfSSL_set_session(ssl_, session);
if (ret != WOLFSSL_SUCCESS) {
std::cerr << "Could not install TLS session from file "
<< config.session_file << std::endl;
} else {
if (!config.disable_early_data &&
wolfSSL_SESSION_get_max_early_data(session)) {
early_data_enabled = true;
wolfSSL_set_quic_early_data_enabled(ssl_, 1);
}
}
wolfSSL_SESSION_free(session);
}
}
wolfSSL_OPENSSL_free(name);
wolfSSL_OPENSSL_free(header);
wolfSSL_OPENSSL_free(data);
}
wolfSSL_BIO_free(f);
}
wolfSSL_UseSessionTicket(ssl_);
wolfSSL_set_SessionTicket_cb(ssl_, wolfssl_session_ticket_cb, nullptr);
#else // !defined(HAVE_SESSION_TICKET)
std::cerr << "TLS session im-/export not enabled in wolfSSL" << std::endl;
#endif // !defined(HAVE_SESSION_TICKET)
}
return 0;
}
bool TLSClientSession::get_early_data_accepted() const {
// wolfSSL_get_early_data_status works after handshake completes.
#ifdef WOLFSSL_EARLY_DATA
return wolfSSL_get_early_data_status(ssl_) == SSL_EARLY_DATA_ACCEPTED;
#else // !defined(WOLFSSL_EARLY_DATA)
return 0;
#endif // !defined(WOLFSSL_EARLY_DATA)
}

View File

@ -0,0 +1,54 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_CLIENT_SESSION_WOLFSSL_H
#define TLS_CLIENT_SESSION_WOLFSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include "tls_session_base_wolfssl.h"
#include "shared.h"
using namespace ngtcp2;
class TLSClientContext;
class ClientBase;
class TLSClientSession : public TLSSessionBase {
public:
TLSClientSession();
~TLSClientSession();
int init(bool &early_data_enabled, const TLSClientContext &tls_ctx,
const char *remote_addr, ClientBase *client, uint32_t quic_version,
AppProtocol app_proto);
bool get_early_data_accepted() const;
bool get_ech_accepted() const { return false; }
int write_ech_config_list(const char *path) const { return 0; }
};
#endif // !defined(TLS_CLIENT_SESSION_WOLFSSL_H)

View File

@ -0,0 +1,56 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SERVER_CONTEXT_H
#define TLS_SERVER_CONTEXT_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#ifdef WITH_EXAMPLE_QUICTLS
# include "tls_server_context_quictls.h"
#endif // defined(WITH_EXAMPLE_QUICTLS)
#ifdef WITH_EXAMPLE_GNUTLS
# include "tls_server_context_gnutls.h"
#endif // defined(WITH_EXAMPLE_GNUTLS)
#ifdef WITH_EXAMPLE_BORINGSSL
# include "tls_server_context_boringssl.h"
#endif // defined(WITH_EXAMPLE_BORINGSSL)
#ifdef WITH_EXAMPLE_PICOTLS
# include "tls_server_context_picotls.h"
#endif // defined(WITH_EXAMPLE_PICOTLS)
#ifdef WITH_EXAMPLE_WOLFSSL
# include "tls_server_context_wolfssl.h"
#endif // defined(WITH_EXAMPLE_WOLFSSL)
#ifdef WITH_EXAMPLE_OSSL
# include "tls_server_context_ossl.h"
#endif // defined(WITH_EXAMPLE_OSSL)
#endif // !defined(TLS_SERVER_CONTEXT_H)

View File

@ -0,0 +1,265 @@
/*
* ngtcp2
*
* Copyright (c) 2021 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_server_context_boringssl.h"
#include <cstring>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <ngtcp2/ngtcp2_crypto_boringssl.h>
#include <openssl/err.h>
#include <openssl/hpke.h>
#include "server_base.h"
#include "template.h"
#include "tls_shared_boringssl.h"
extern Config config;
TLSServerContext::TLSServerContext() : ssl_ctx_{nullptr} {}
TLSServerContext::~TLSServerContext() {
if (ssl_ctx_) {
SSL_CTX_free(ssl_ctx_);
}
}
SSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; }
namespace {
int alpn_select_proto_h3_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
// This should be the negotiated version, but we have not set the
// negotiated version when this callback is called.
auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
switch (version) {
case NGTCP2_PROTO_VER_V1:
case NGTCP2_PROTO_VER_V2:
break;
default:
if (!config.quiet) {
std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
<< version << std::dec << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
for (auto s = std::span{in, inlen}; s.size() >= H3_ALPN_V1.size();
s = s.subspan(s[0] + 1)) {
if (std::ranges::equal(H3_ALPN_V1, s.first(H3_ALPN_V1.size()))) {
*out = &s[1];
*outlen = s[0];
return SSL_TLSEXT_ERR_OK;
}
}
if (!config.quiet) {
std::cerr << "Client did not present ALPN " << &H3_ALPN_V1[1] << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
} // namespace
namespace {
int alpn_select_proto_hq_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
// This should be the negotiated version, but we have not set the
// negotiated version when this callback is called.
auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
switch (version) {
case NGTCP2_PROTO_VER_V1:
case NGTCP2_PROTO_VER_V2:
break;
default:
if (!config.quiet) {
std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
<< version << std::dec << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
for (auto s = std::span{in, inlen}; s.size() >= HQ_ALPN_V1.size();
s = s.subspan(s[0] + 1)) {
if (std::ranges::equal(HQ_ALPN_V1, s.first(HQ_ALPN_V1.size()))) {
*out = &s[1];
*outlen = s[0];
return SSL_TLSEXT_ERR_OK;
}
}
if (!config.quiet) {
std::cerr << "Client did not present ALPN " << &HQ_ALPN_V1[1] << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
} // namespace
namespace {
int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) {
// We don't verify the client certificate. Just request it for the
// testing purpose.
return 1;
}
} // namespace
int TLSServerContext::init(const char *private_key_file, const char *cert_file,
AppProtocol app_proto) {
constexpr static unsigned char sid_ctx[] = "ngtcp2 server";
ssl_ctx_ = SSL_CTX_new(TLS_server_method());
if (!ssl_ctx_) {
std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
constexpr auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
SSL_OP_SINGLE_ECDH_USE |
SSL_OP_CIPHER_SERVER_PREFERENCE;
SSL_CTX_set_options(ssl_ctx_, ssl_opts);
if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) {
std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
return -1;
}
SSL_CTX_set_mode(ssl_ctx_, SSL_MODE_RELEASE_BUFFERS);
if (ngtcp2_crypto_boringssl_configure_server_context(ssl_ctx_) != 0) {
std::cerr << "ngtcp2_crypto_boringssl_configure_server_context failed"
<< std::endl;
return -1;
}
switch (app_proto) {
case AppProtocol::H3:
SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_h3_cb, nullptr);
break;
case AppProtocol::HQ:
SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_hq_cb, nullptr);
break;
}
SSL_CTX_set_default_verify_paths(ssl_ctx_);
if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
SSL_FILETYPE_PEM) != 1) {
std::cerr << "SSL_CTX_use_PrivateKey_file: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) {
std::cerr << "SSL_CTX_use_certificate_chain_file: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_check_private_key(ssl_ctx_) != 1) {
std::cerr << "SSL_CTX_check_private_key: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
SSL_CTX_set_session_id_context(ssl_ctx_, sid_ctx, sizeof(sid_ctx) - 1);
if (config.verify_client) {
SSL_CTX_set_verify(ssl_ctx_,
SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
verify_cb);
}
#ifdef HAVE_LIBBROTLI
if (!SSL_CTX_add_cert_compression_alg(
ssl_ctx_, ngtcp2::tls::CERTIFICATE_COMPRESSION_ALGO_BROTLI,
ngtcp2::tls::cert_compress, ngtcp2::tls::cert_decompress)) {
std::cerr << "SSL_CTX_add_cert_compression_alg failed" << std::endl;
return -1;
}
#endif // defined(HAVE_LIBBROTLI)
if (!config.ech_config.ech_config.empty()) {
const auto &echconf = config.ech_config;
auto pkey = EVP_HPKE_KEY_new();
if (EVP_HPKE_KEY_init(pkey, EVP_hpke_x25519_hkdf_sha256(),
echconf.private_key.bytes.data(),
echconf.private_key.bytes.size()) != 1) {
std::cerr << "EVP_HPKE_KEY_init failed: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
auto pkey_d = defer(EVP_HPKE_KEY_free, pkey);
auto keys = SSL_ECH_KEYS_new();
auto keys_d = defer(SSL_ECH_KEYS_free, keys);
if (SSL_ECH_KEYS_add(keys, 1, echconf.ech_config.data(),
echconf.ech_config.size(), pkey) != 1) {
std::cerr << "SSL_ECH_KEYS_add failed: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_set1_ech_keys(ssl_ctx_, keys) != 1) {
std::cerr << "SSL_CTX_set1_ech_keys failed: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
}
return 0;
}
extern std::ofstream keylog_file;
namespace {
void keylog_callback(const SSL *ssl, const char *line) {
keylog_file.write(line, static_cast<std::streamsize>(strlen(line)));
keylog_file.put('\n');
keylog_file.flush();
}
} // namespace
void TLSServerContext::enable_keylog() {
SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
}

View File

@ -0,0 +1,54 @@
/*
* ngtcp2
*
* Copyright (c) 2021 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SERVER_CONTEXT_BORINGSSL_H
#define TLS_SERVER_CONTEXT_BORINGSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <openssl/ssl.h>
#include "shared.h"
using namespace ngtcp2;
class TLSServerContext {
public:
TLSServerContext();
~TLSServerContext();
int init(const char *private_key_file, const char *cert_file,
AppProtocol app_proto);
SSL_CTX *get_native_handle() const;
void enable_keylog();
private:
SSL_CTX *ssl_ctx_;
};
#endif // !defined(TLS_SERVER_CONTEXT_BORINGSSL_H)

View File

@ -0,0 +1,307 @@
/*
* ngtcp2
*
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_server_context_ossl.h"
#include <cstring>
#include <cassert>
#include <iostream>
#include <fstream>
#include <limits>
#include <algorithm>
#include <ngtcp2/ngtcp2_crypto_ossl.h>
#include <openssl/err.h>
#include "server_base.h"
#include "template.h"
namespace {
auto _ = []() {
if (ngtcp2_crypto_ossl_init() != 0) {
assert(0);
abort();
}
return 0;
}();
} // namespace
extern Config config;
TLSServerContext::TLSServerContext() : ssl_ctx_{nullptr} {}
TLSServerContext::~TLSServerContext() {
if (ssl_ctx_) {
SSL_CTX_free(ssl_ctx_);
}
}
SSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; }
namespace {
int alpn_select_proto_h3_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
// This should be the negotiated version, but we have not set the
// negotiated version when this callback is called.
auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
switch (version) {
case NGTCP2_PROTO_VER_V1:
case NGTCP2_PROTO_VER_V2:
break;
default:
if (!config.quiet) {
std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
<< version << std::dec << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
for (auto s = std::span{in, inlen}; s.size() >= H3_ALPN_V1.size();
s = s.subspan(s[0] + 1)) {
if (std::ranges::equal(H3_ALPN_V1, s.first(H3_ALPN_V1.size()))) {
*out = &s[1];
*outlen = s[0];
return SSL_TLSEXT_ERR_OK;
}
}
if (!config.quiet) {
std::cerr << "Client did not present ALPN " << &H3_ALPN_V1[1] << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
} // namespace
namespace {
int alpn_select_proto_hq_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
// This should be the negotiated version, but we have not set the
// negotiated version when this callback is called.
auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
switch (version) {
case NGTCP2_PROTO_VER_V1:
case NGTCP2_PROTO_VER_V2:
break;
default:
if (!config.quiet) {
std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
<< version << std::dec << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
for (auto s = std::span{in, inlen}; s.size() >= HQ_ALPN_V1.size();
s = s.subspan(s[0] + 1)) {
if (std::ranges::equal(HQ_ALPN_V1, s.first(HQ_ALPN_V1.size()))) {
*out = &s[1];
*outlen = s[0];
return SSL_TLSEXT_ERR_OK;
}
}
if (!config.quiet) {
std::cerr << "Client did not present ALPN " << &HQ_ALPN_V1[1] << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
} // namespace
namespace {
int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) {
// We don't verify the client certificate. Just request it for the
// testing purpose.
return 1;
}
} // namespace
namespace {
int gen_ticket_cb(SSL *ssl, void *arg) {
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
auto ver = htonl(ngtcp2_conn_get_negotiated_version(h->conn()));
if (!SSL_SESSION_set1_ticket_appdata(SSL_get0_session(ssl), &ver,
sizeof(ver))) {
return 0;
}
return 1;
}
} // namespace
namespace {
SSL_TICKET_RETURN decrypt_ticket_cb(SSL *ssl, SSL_SESSION *session,
const unsigned char *keyname,
size_t keynamelen, SSL_TICKET_STATUS status,
void *arg) {
switch (status) {
case SSL_TICKET_EMPTY:
case SSL_TICKET_NO_DECRYPT:
return SSL_TICKET_RETURN_IGNORE_RENEW;
}
uint8_t *pver;
uint32_t ver;
size_t verlen;
if (!SSL_SESSION_get0_ticket_appdata(
session, reinterpret_cast<void **>(&pver), &verlen) ||
verlen != sizeof(ver)) {
switch (status) {
case SSL_TICKET_SUCCESS:
return SSL_TICKET_RETURN_IGNORE;
case SSL_TICKET_SUCCESS_RENEW:
default:
return SSL_TICKET_RETURN_IGNORE_RENEW;
}
}
memcpy(&ver, pver, sizeof(ver));
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
if (ngtcp2_conn_get_client_chosen_version(h->conn()) != ntohl(ver)) {
switch (status) {
case SSL_TICKET_SUCCESS:
return SSL_TICKET_RETURN_IGNORE;
case SSL_TICKET_SUCCESS_RENEW:
default:
return SSL_TICKET_RETURN_IGNORE_RENEW;
}
}
switch (status) {
case SSL_TICKET_SUCCESS:
return SSL_TICKET_RETURN_USE;
case SSL_TICKET_SUCCESS_RENEW:
default:
return SSL_TICKET_RETURN_USE_RENEW;
}
}
} // namespace
int TLSServerContext::init(const char *private_key_file, const char *cert_file,
AppProtocol app_proto) {
constexpr static unsigned char sid_ctx[] = "ngtcp2 server";
ssl_ctx_ = SSL_CTX_new(TLS_server_method());
if (!ssl_ctx_) {
std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
SSL_CTX_set_max_early_data(ssl_ctx_, UINT32_MAX);
constexpr auto ssl_opts = (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_options(ssl_ctx_, ssl_opts);
if (SSL_CTX_set_ciphersuites(ssl_ctx_, config.ciphers) != 1) {
std::cerr << "SSL_CTX_set_ciphersuites: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) {
std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
return -1;
}
SSL_CTX_set_mode(ssl_ctx_, SSL_MODE_RELEASE_BUFFERS);
switch (app_proto) {
case AppProtocol::H3:
SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_h3_cb, nullptr);
break;
case AppProtocol::HQ:
SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_hq_cb, nullptr);
break;
}
SSL_CTX_set_default_verify_paths(ssl_ctx_);
if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
SSL_FILETYPE_PEM) != 1) {
std::cerr << "SSL_CTX_use_PrivateKey_file: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) {
std::cerr << "SSL_CTX_use_certificate_chain_file: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_check_private_key(ssl_ctx_) != 1) {
std::cerr << "SSL_CTX_check_private_key: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
SSL_CTX_set_session_id_context(ssl_ctx_, sid_ctx, sizeof(sid_ctx) - 1);
if (config.verify_client) {
SSL_CTX_set_verify(ssl_ctx_,
SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
verify_cb);
}
SSL_CTX_set_session_ticket_cb(ssl_ctx_, gen_ticket_cb, decrypt_ticket_cb,
nullptr);
return 0;
}
extern std::ofstream keylog_file;
namespace {
void keylog_callback(const SSL *ssl, const char *line) {
keylog_file.write(line, static_cast<std::streamsize>(strlen(line)));
keylog_file.put('\n');
keylog_file.flush();
}
} // namespace
void TLSServerContext::enable_keylog() {
SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
}

View File

@ -0,0 +1,54 @@
/*
* ngtcp2
*
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SERVER_CONTEXT_OSSL_H
#define TLS_SERVER_CONTEXT_OSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <openssl/ssl.h>
#include "shared.h"
using namespace ngtcp2;
class TLSServerContext {
public:
TLSServerContext();
~TLSServerContext();
int init(const char *private_key_file, const char *cert_file,
AppProtocol app_proto);
SSL_CTX *get_native_handle() const;
void enable_keylog();
private:
SSL_CTX *ssl_ctx_;
};
#endif // !defined(TLS_SERVER_CONTEXT_OSSL_H)

View File

@ -0,0 +1,373 @@
/*
* ngtcp2
*
* Copyright (c) 2022 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_server_context_picotls.h"
#include <iostream>
#include <memory>
#include <algorithm>
#include <ngtcp2/ngtcp2_crypto_picotls.h>
#include <openssl/pem.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
# include <openssl/core_names.h>
#endif // OPENSSL_VERSION_NUMBER >= 0x30000000L
#include "server_base.h"
#include "tls_shared_picotls.h"
#include "template.h"
extern Config config;
namespace {
int on_client_hello_h3_cb(ptls_on_client_hello_t *self, ptls_t *ptls,
ptls_on_client_hello_parameters_t *params) {
auto &negprotos = params->negotiated_protocols;
for (size_t i = 0; i < negprotos.count; ++i) {
auto &proto = negprotos.list[i];
if (std::ranges::equal(H3_ALPN_V1.subspan(1),
std::span{proto.base, proto.len})) {
if (ptls_set_negotiated_protocol(
ptls, reinterpret_cast<char *>(proto.base), proto.len) != 0) {
return -1;
}
return 0;
}
}
return PTLS_ALERT_NO_APPLICATION_PROTOCOL;
}
ptls_on_client_hello_t on_client_hello_h3 = {on_client_hello_h3_cb};
} // namespace
namespace {
int on_client_hello_hq_cb(ptls_on_client_hello_t *self, ptls_t *ptls,
ptls_on_client_hello_parameters_t *params) {
auto &negprotos = params->negotiated_protocols;
for (size_t i = 0; i < negprotos.count; ++i) {
auto &proto = negprotos.list[i];
if (std::ranges::equal(HQ_ALPN_V1.subspan(1),
std::span{proto.base, proto.len})) {
if (ptls_set_negotiated_protocol(
ptls, reinterpret_cast<char *>(proto.base), proto.len) != 0) {
return -1;
}
return 0;
}
}
return PTLS_ALERT_NO_APPLICATION_PROTOCOL;
}
ptls_on_client_hello_t on_client_hello_hq = {on_client_hello_hq_cb};
} // namespace
namespace {
auto ticket_hmac = EVP_sha256();
std::span<const uint8_t> get_ticket_key_name() {
static std::array<uint8_t, 16> key_name;
ptls_openssl_random_bytes(key_name.data(), key_name.size());
return key_name;
}
std::span<const uint8_t> get_ticket_key() {
static std::array<uint8_t, 32> key;
ptls_openssl_random_bytes(key.data(), key.size());
return key;
}
std::span<const uint8_t> get_ticket_hmac_key() {
static std::array<uint8_t, 32> hmac_key;
ptls_openssl_random_bytes(hmac_key.data(), hmac_key.size());
return hmac_key;
}
} // namespace
namespace {
int ticket_key_cb(unsigned char *key_name, unsigned char *iv,
EVP_CIPHER_CTX *ctx,
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
EVP_MAC_CTX *hctx,
#else // OPENSSL_VERSION_NUMBER < 0x30000000L
HMAC_CTX *hctx,
#endif // OPENSSL_VERSION_NUMBER < 0x30000000L
int enc) {
static const auto static_key_name = get_ticket_key_name();
static const auto static_key = get_ticket_key();
static const auto static_hmac_key = get_ticket_hmac_key();
if (enc) {
ptls_openssl_random_bytes(iv, EVP_MAX_IV_LENGTH);
std::ranges::copy(static_key_name, key_name);
if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, static_key.data(),
iv)) {
return 0;
}
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
auto params = std::to_array({
OSSL_PARAM_construct_octet_string(
OSSL_MAC_PARAM_KEY, const_cast<uint8_t *>(static_hmac_key.data()),
static_hmac_key.size()),
OSSL_PARAM_construct_utf8_string(
OSSL_MAC_PARAM_DIGEST,
const_cast<char *>(EVP_MD_get0_name(ticket_hmac)), 0),
OSSL_PARAM_construct_end(),
});
if (!EVP_MAC_CTX_set_params(hctx, params.data())) {
/* TODO Which value should we return on error? */
return 0;
}
#else // OPENSSL_VERSION_NUMBER < 0x30000000L
if (!HMAC_Init_ex(hctx, static_hmac_key.data(),
static_cast<int>(static_hmac_key.size()), ticket_hmac,
nullptr)) {
return 0;
}
#endif // OPENSSL_VERSION_NUMBER < 0x30000000L
return 1;
}
if (!std::ranges::equal(std::span{key_name, static_key_name.size()},
static_key_name)) {
return 0;
}
if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, static_key.data(),
iv)) {
return 0;
}
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
auto params = std::to_array({
OSSL_PARAM_construct_octet_string(
OSSL_MAC_PARAM_KEY, const_cast<uint8_t *>(static_hmac_key.data()),
static_hmac_key.size()),
OSSL_PARAM_construct_utf8_string(
OSSL_MAC_PARAM_DIGEST, const_cast<char *>(EVP_MD_get0_name(ticket_hmac)),
0),
OSSL_PARAM_construct_end(),
});
if (!EVP_MAC_CTX_set_params(hctx, params.data())) {
/* TODO Which value should we return on error? */
return 0;
}
#else // OPENSSL_VERSION_NUMBER < 0x30000000L
if (!HMAC_Init_ex(hctx, static_hmac_key.data(),
static_cast<int>(static_hmac_key.size()), ticket_hmac,
nullptr)) {
return 0;
}
#endif // OPENSSL_VERSION_NUMBER < 0x30000000L
return 1;
}
} // namespace
namespace {
int encrypt_ticket_cb(ptls_encrypt_ticket_t *encrypt_ticket, ptls_t *ptls,
int is_encrypt, ptls_buffer_t *dst, ptls_iovec_t src) {
int rv;
auto conn_ref =
static_cast<ngtcp2_crypto_conn_ref *>(*ptls_get_data_ptr(ptls));
auto conn = conn_ref->get_conn(conn_ref);
uint32_t ver;
if (is_encrypt) {
ver = htonl(ngtcp2_conn_get_negotiated_version(conn));
// TODO Replace std::make_unique with
// std::make_unique_for_overwrite when it is available.
auto buf = std::make_unique<uint8_t[]>(src.len + sizeof(ver));
auto p = std::ranges::copy_n(src.base, as_signed(src.len), buf.get()).out;
p = std::ranges::copy_n(reinterpret_cast<uint8_t *>(&ver), sizeof(ver), p)
.out;
src.base = buf.get();
src.len = as_unsigned(p - buf.get());
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
rv = ptls_openssl_encrypt_ticket_evp(dst, src, ticket_key_cb);
#else // OPENSSL_VERSION_NUMBER < 0x30000000L
rv = ptls_openssl_encrypt_ticket(dst, src, ticket_key_cb);
#endif // OPENSSL_VERSION_NUMBER < 0x30000000L
if (rv != 0) {
return -1;
}
return 0;
}
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
rv = ptls_openssl_decrypt_ticket_evp(dst, src, ticket_key_cb);
#else // OPENSSL_VERSION_NUMBER < 0x30000000L
rv = ptls_openssl_decrypt_ticket(dst, src, ticket_key_cb);
#endif // OPENSSL_VERSION_NUMBER < 0x30000000L
if (rv != 0) {
return -1;
}
if (dst->off < sizeof(ver)) {
return -1;
}
memcpy(&ver, dst->base + dst->off - sizeof(ver), sizeof(ver));
if (ngtcp2_conn_get_client_chosen_version(conn) != ntohl(ver)) {
return -1;
}
dst->off -= sizeof(ver);
return 0;
}
ptls_encrypt_ticket_t encrypt_ticket = {encrypt_ticket_cb};
} // namespace
namespace {
ptls_key_exchange_algorithm_t *key_exchanges[] = {
#if PTLS_OPENSSL_HAVE_X25519
&ptls_openssl_x25519,
#endif // PTLS_OPENSSL_X25519
&ptls_openssl_secp256r1,
&ptls_openssl_secp384r1,
&ptls_openssl_secp521r1,
#if PTLS_OPENSSL_HAVE_X25519MLKEM768
&ptls_openssl_x25519mlkem768,
#endif // PTLS_OPENSSL_HAVE_X25519MLKEM768
nullptr,
};
} // namespace
namespace {
ptls_cipher_suite_t *cipher_suites[] = {
&ptls_openssl_aes128gcmsha256,
&ptls_openssl_aes256gcmsha384,
#if PTLS_OPENSSL_HAVE_CHACHA20_POLY1305
&ptls_openssl_chacha20poly1305sha256,
#endif // PTLS_OPENSSL_CHACHA20POLY1305SHA256
nullptr,
};
} // namespace
TLSServerContext::TLSServerContext()
: ctx_{
.random_bytes = ptls_openssl_random_bytes,
.get_time = &ptls_get_time,
.key_exchanges = key_exchanges,
.cipher_suites = cipher_suites,
.ticket_lifetime = 86400,
.require_dhe_on_psk = 1,
.server_cipher_preference = 1,
.encrypt_ticket = &encrypt_ticket,
},
sign_cert_{}
{}
TLSServerContext::~TLSServerContext() {
if (sign_cert_.key) {
ptls_openssl_dispose_sign_certificate(&sign_cert_);
}
for (size_t i = 0; i < ctx_.certificates.count; ++i) {
free(ctx_.certificates.list[i].base);
}
free(ctx_.certificates.list);
}
ptls_context_t *TLSServerContext::get_native_handle() { return &ctx_; }
int TLSServerContext::init(const char *private_key_file, const char *cert_file,
AppProtocol app_proto) {
switch (app_proto) {
case AppProtocol::H3:
ctx_.on_client_hello = &on_client_hello_h3;
break;
case AppProtocol::HQ:
ctx_.on_client_hello = &on_client_hello_hq;
break;
};
if (ngtcp2_crypto_picotls_configure_server_context(&ctx_) != 0) {
std::cerr << "ngtcp2_crypto_picotls_configure_server_context failed"
<< std::endl;
return -1;
}
if (ptls_load_certificates(&ctx_, cert_file) != 0) {
std::cerr << "ptls_load_certificates failed" << std::endl;
return -1;
}
if (load_private_key(private_key_file) != 0) {
return -1;
}
if (config.verify_client) {
ctx_.require_client_authentication = 1;
}
return 0;
}
int TLSServerContext::load_private_key(const char *private_key_file) {
auto fp = fopen(private_key_file, "rb");
if (fp == nullptr) {
std::cerr << "Could not open private key file " << private_key_file << ": "
<< strerror(errno) << std::endl;
return -1;
}
auto fp_d = defer(fclose, fp);
auto pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr);
if (pkey == nullptr) {
std::cerr << "Could not read private key file " << private_key_file
<< std::endl;
return -1;
}
auto pkey_d = defer(EVP_PKEY_free, pkey);
if (ptls_openssl_init_sign_certificate(&sign_cert_, pkey) != 0) {
std::cerr << "ptls_openssl_init_sign_certificate failed" << std::endl;
return -1;
}
ctx_.sign_certificate = &sign_cert_.super;
return 0;
}
void TLSServerContext::enable_keylog() { ctx_.log_event = &log_event; }

View File

@ -0,0 +1,58 @@
/*
* ngtcp2
*
* Copyright (c) 2022 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SERVER_CONTEXT_PICOTLS_H
#define TLS_SERVER_CONTEXT_PICOTLS_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <picotls.h>
#include <picotls/openssl.h>
#include "shared.h"
using namespace ngtcp2;
class TLSServerContext {
public:
TLSServerContext();
~TLSServerContext();
int init(const char *private_key_file, const char *cert_file,
AppProtocol app_proto);
ptls_context_t *get_native_handle();
void enable_keylog();
private:
int load_private_key(const char *private_key_file);
ptls_context_t ctx_;
ptls_openssl_sign_certificate_t sign_cert_;
};
#endif // !defined(TLS_SERVER_CONTEXT_PICOTLS_H)

View File

@ -0,0 +1,320 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_server_context_quictls.h"
#include <cstring>
#include <cassert>
#include <iostream>
#include <fstream>
#include <limits>
#include <algorithm>
#include <ngtcp2/ngtcp2_crypto_quictls.h>
#include <openssl/err.h>
#include "server_base.h"
#include "template.h"
namespace {
auto _ = []() {
if (ngtcp2_crypto_quictls_init() != 0) {
assert(0);
abort();
}
return 0;
}();
} // namespace
extern Config config;
TLSServerContext::TLSServerContext() : ssl_ctx_{nullptr} {}
TLSServerContext::~TLSServerContext() {
if (ssl_ctx_) {
SSL_CTX_free(ssl_ctx_);
}
}
SSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; }
namespace {
int alpn_select_proto_h3_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
// This should be the negotiated version, but we have not set the
// negotiated version when this callback is called.
auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
switch (version) {
case NGTCP2_PROTO_VER_V1:
case NGTCP2_PROTO_VER_V2:
break;
default:
if (!config.quiet) {
std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
<< version << std::dec << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
for (auto s = std::span{in, inlen}; s.size() >= H3_ALPN_V1.size();
s = s.subspan(s[0] + 1)) {
if (std::ranges::equal(H3_ALPN_V1, s.first(H3_ALPN_V1.size()))) {
*out = &s[1];
*outlen = s[0];
return SSL_TLSEXT_ERR_OK;
}
}
if (!config.quiet) {
std::cerr << "Client did not present ALPN " << &H3_ALPN_V1[1] << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
} // namespace
namespace {
int alpn_select_proto_hq_cb(SSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
// This should be the negotiated version, but we have not set the
// negotiated version when this callback is called.
auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
switch (version) {
case NGTCP2_PROTO_VER_V1:
case NGTCP2_PROTO_VER_V2:
break;
default:
if (!config.quiet) {
std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
<< version << std::dec << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
for (auto s = std::span{in, inlen}; s.size() >= HQ_ALPN_V1.size();
s = s.subspan(s[0] + 1)) {
if (std::ranges::equal(HQ_ALPN_V1, s.first(HQ_ALPN_V1.size()))) {
*out = &s[1];
*outlen = s[0];
return SSL_TLSEXT_ERR_OK;
}
}
if (!config.quiet) {
std::cerr << "Client did not present ALPN " << &HQ_ALPN_V1[1] << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
} // namespace
namespace {
int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) {
// We don't verify the client certificate. Just request it for the
// testing purpose.
return 1;
}
} // namespace
#ifndef LIBRESSL_VERSION_NUMBER
namespace {
int gen_ticket_cb(SSL *ssl, void *arg) {
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
auto ver = htonl(ngtcp2_conn_get_negotiated_version(h->conn()));
if (!SSL_SESSION_set1_ticket_appdata(SSL_get0_session(ssl), &ver,
sizeof(ver))) {
return 0;
}
return 1;
}
} // namespace
namespace {
SSL_TICKET_RETURN decrypt_ticket_cb(SSL *ssl, SSL_SESSION *session,
const unsigned char *keyname,
size_t keynamelen, SSL_TICKET_STATUS status,
void *arg) {
switch (status) {
case SSL_TICKET_EMPTY:
case SSL_TICKET_NO_DECRYPT:
return SSL_TICKET_RETURN_IGNORE_RENEW;
}
uint8_t *pver;
uint32_t ver;
size_t verlen;
if (!SSL_SESSION_get0_ticket_appdata(
session, reinterpret_cast<void **>(&pver), &verlen) ||
verlen != sizeof(ver)) {
switch (status) {
case SSL_TICKET_SUCCESS:
return SSL_TICKET_RETURN_IGNORE;
case SSL_TICKET_SUCCESS_RENEW:
default:
return SSL_TICKET_RETURN_IGNORE_RENEW;
}
}
memcpy(&ver, pver, sizeof(ver));
auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl));
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
if (ngtcp2_conn_get_client_chosen_version(h->conn()) != ntohl(ver)) {
switch (status) {
case SSL_TICKET_SUCCESS:
return SSL_TICKET_RETURN_IGNORE;
case SSL_TICKET_SUCCESS_RENEW:
default:
return SSL_TICKET_RETURN_IGNORE_RENEW;
}
}
switch (status) {
case SSL_TICKET_SUCCESS:
return SSL_TICKET_RETURN_USE;
case SSL_TICKET_SUCCESS_RENEW:
default:
return SSL_TICKET_RETURN_USE_RENEW;
}
}
} // namespace
#endif // !defined(LIBRESSL_VERSION_NUMBER)
int TLSServerContext::init(const char *private_key_file, const char *cert_file,
AppProtocol app_proto) {
constexpr static unsigned char sid_ctx[] = "ngtcp2 server";
ssl_ctx_ = SSL_CTX_new(TLS_server_method());
if (!ssl_ctx_) {
std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
if (ngtcp2_crypto_quictls_configure_server_context(ssl_ctx_) != 0) {
std::cerr << "ngtcp2_crypto_quictls_configure_server_context failed"
<< std::endl;
return -1;
}
SSL_CTX_set_max_early_data(ssl_ctx_, UINT32_MAX);
constexpr auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
SSL_OP_SINGLE_ECDH_USE |
SSL_OP_CIPHER_SERVER_PREFERENCE
#ifndef LIBRESSL_VERSION_NUMBER
| SSL_OP_NO_ANTI_REPLAY
#endif // !defined(LIBRESSL_VERSION_NUMBER)
;
SSL_CTX_set_options(ssl_ctx_, ssl_opts);
if (SSL_CTX_set_ciphersuites(ssl_ctx_, config.ciphers) != 1) {
std::cerr << "SSL_CTX_set_ciphersuites: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) {
std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl;
return -1;
}
SSL_CTX_set_mode(ssl_ctx_, SSL_MODE_RELEASE_BUFFERS);
switch (app_proto) {
case AppProtocol::H3:
SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_h3_cb, nullptr);
break;
case AppProtocol::HQ:
SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_hq_cb, nullptr);
break;
}
SSL_CTX_set_default_verify_paths(ssl_ctx_);
if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
SSL_FILETYPE_PEM) != 1) {
std::cerr << "SSL_CTX_use_PrivateKey_file: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) {
std::cerr << "SSL_CTX_use_certificate_chain_file: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (SSL_CTX_check_private_key(ssl_ctx_) != 1) {
std::cerr << "SSL_CTX_check_private_key: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
SSL_CTX_set_session_id_context(ssl_ctx_, sid_ctx, sizeof(sid_ctx) - 1);
if (config.verify_client) {
SSL_CTX_set_verify(ssl_ctx_,
SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
verify_cb);
}
#ifndef LIBRESSL_VERSION_NUMBER
SSL_CTX_set_session_ticket_cb(ssl_ctx_, gen_ticket_cb, decrypt_ticket_cb,
nullptr);
#endif // !defined(LIBRESSL_VERSION_NUMBER)
return 0;
}
extern std::ofstream keylog_file;
namespace {
void keylog_callback(const SSL *ssl, const char *line) {
keylog_file.write(line, static_cast<std::streamsize>(strlen(line)));
keylog_file.put('\n');
keylog_file.flush();
}
} // namespace
void TLSServerContext::enable_keylog() {
SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
}

View File

@ -0,0 +1,54 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SERVER_CONTEXT_QUICTLS_H
#define TLS_SERVER_CONTEXT_QUICTLS_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <openssl/ssl.h>
#include "shared.h"
using namespace ngtcp2;
class TLSServerContext {
public:
TLSServerContext();
~TLSServerContext();
int init(const char *private_key_file, const char *cert_file,
AppProtocol app_proto);
SSL_CTX *get_native_handle() const;
void enable_keylog();
private:
SSL_CTX *ssl_ctx_;
};
#endif // !defined(TLS_SERVER_CONTEXT_QUICTLS_H)

View File

@ -0,0 +1,248 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_server_context_wolfssl.h"
#include <cstring>
#include <iostream>
#include <fstream>
#include <limits>
#include <algorithm>
#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
#include "server_base.h"
#include "template.h"
extern Config config;
TLSServerContext::TLSServerContext() : ssl_ctx_{nullptr} {}
TLSServerContext::~TLSServerContext() {
if (ssl_ctx_) {
wolfSSL_CTX_free(ssl_ctx_);
}
}
WOLFSSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; }
namespace {
int alpn_select_proto_h3_cb(WOLFSSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
auto conn_ref =
static_cast<ngtcp2_crypto_conn_ref *>(wolfSSL_get_app_data(ssl));
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
// This should be the negotiated version, but we have not set the
// negotiated version when this callback is called.
auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
switch (version) {
case NGTCP2_PROTO_VER_V1:
case NGTCP2_PROTO_VER_V2:
break;
default:
if (!config.quiet) {
std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
<< version << std::dec << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
for (auto s = std::span{in, inlen}; s.size() >= H3_ALPN_V1.size();
s = s.subspan(s[0] + 1)) {
if (std::ranges::equal(H3_ALPN_V1, s.first(H3_ALPN_V1.size()))) {
*out = &s[1];
*outlen = s[0];
return SSL_TLSEXT_ERR_OK;
}
}
if (!config.quiet) {
std::cerr << "Client did not present ALPN " << &H3_ALPN_V1[1] << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
} // namespace
namespace {
int alpn_select_proto_hq_cb(WOLFSSL *ssl, const unsigned char **out,
unsigned char *outlen, const unsigned char *in,
unsigned int inlen, void *arg) {
auto conn_ref =
static_cast<ngtcp2_crypto_conn_ref *>(wolfSSL_get_app_data(ssl));
auto h = static_cast<HandlerBase *>(conn_ref->user_data);
// This should be the negotiated version, but we have not set the
// negotiated version when this callback is called.
auto version = ngtcp2_conn_get_client_chosen_version(h->conn());
switch (version) {
case NGTCP2_PROTO_VER_V1:
case NGTCP2_PROTO_VER_V2:
break;
default:
if (!config.quiet) {
std::cerr << "Unexpected quic protocol version: " << std::hex << "0x"
<< version << std::dec << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
for (auto s = std::span{in, inlen}; s.size() >= HQ_ALPN_V1.size();
s = s.subspan(s[0] + 1)) {
if (std::ranges::equal(HQ_ALPN_V1, s.first(HQ_ALPN_V1.size()))) {
*out = &s[1];
*outlen = s[0];
return SSL_TLSEXT_ERR_OK;
}
}
if (!config.quiet) {
std::cerr << "Client did not present ALPN " << &HQ_ALPN_V1[1] << std::endl;
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
} // namespace
namespace {
int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) {
// We don't verify the client certificate. Just request it for the
// testing purpose.
return 1;
}
} // namespace
int TLSServerContext::init(const char *private_key_file, const char *cert_file,
AppProtocol app_proto) {
constexpr static unsigned char sid_ctx[] = "ngtcp2 server";
#ifdef DEBUG_WOLFSSL
if (!config.quiet) {
/*wolfSSL_Debugging_ON();*/
}
#endif // defined(DEBUG_WOLFSSL)
ssl_ctx_ = wolfSSL_CTX_new(wolfTLSv1_3_server_method());
if (!ssl_ctx_) {
std::cerr << "wolfSSL_CTX_new: "
<< wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
if (ngtcp2_crypto_wolfssl_configure_server_context(ssl_ctx_) != 0) {
std::cerr << "ngtcp2_crypto_wolfssl_configure_server_context failed"
<< std::endl;
return -1;
}
#ifdef WOLFSSL_EARLY_DATA
wolfSSL_CTX_set_max_early_data(ssl_ctx_, UINT32_MAX);
#endif // defined(WOLFSSL_EARLY_DATA)
constexpr auto ssl_opts =
(WOLFSSL_OP_ALL & ~WOLFSSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
WOLFSSL_OP_SINGLE_ECDH_USE | WOLFSSL_OP_CIPHER_SERVER_PREFERENCE;
wolfSSL_CTX_set_options(ssl_ctx_, ssl_opts);
if (wolfSSL_CTX_set_cipher_list(ssl_ctx_, config.ciphers) != 1) {
std::cerr << "wolfSSL_CTX_set_cipher_list: "
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
return -1;
}
if (wolfSSL_CTX_set1_groups_list(ssl_ctx_,
const_cast<char *>(config.groups)) != 1) {
std::cerr << "wolfSSL_CTX_set1_groups_list(" << config.groups << ") failed"
<< std::endl;
return -1;
}
wolfSSL_CTX_set_mode(ssl_ctx_, SSL_MODE_RELEASE_BUFFERS);
switch (app_proto) {
case AppProtocol::H3:
wolfSSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_h3_cb, nullptr);
break;
case AppProtocol::HQ:
wolfSSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_hq_cb, nullptr);
break;
}
wolfSSL_CTX_set_default_verify_paths(ssl_ctx_);
if (wolfSSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file,
SSL_FILETYPE_PEM) != 1) {
std::cerr << "wolfSSL_CTX_use_PrivateKey_file: "
<< wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
if (wolfSSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) {
std::cerr << "wolfSSL_CTX_use_certificate_chain_file: "
<< wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
if (wolfSSL_CTX_check_private_key(ssl_ctx_) != 1) {
std::cerr << "wolfSSL_CTX_check_private_key: "
<< wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
wolfSSL_CTX_set_session_id_context(ssl_ctx_, sid_ctx, sizeof(sid_ctx) - 1);
if (config.verify_client) {
wolfSSL_CTX_set_verify(ssl_ctx_,
WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_CLIENT_ONCE |
WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT,
verify_cb);
}
return 0;
}
extern std::ofstream keylog_file;
#ifdef HAVE_SECRET_CALLBACK
namespace {
void keylog_callback(const WOLFSSL *ssl, const char *line) {
keylog_file.write(line, static_cast<std::streamsize>(strlen(line)));
keylog_file.put('\n');
keylog_file.flush();
}
} // namespace
#endif // defined(HAVE_SECRET_CALLBACK)
void TLSServerContext::enable_keylog() {
#ifdef HAVE_SECRET_CALLBACK
wolfSSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback);
#endif // defined(HAVE_SECRET_CALLBACK)
}

View File

@ -0,0 +1,55 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SERVER_CONTEXT_WOLFSSL_H
#define TLS_SERVER_CONTEXT_WOLFSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#include "shared.h"
using namespace ngtcp2;
class TLSServerContext {
public:
TLSServerContext();
~TLSServerContext();
int init(const char *private_key_file, const char *cert_file,
AppProtocol app_proto);
WOLFSSL_CTX *get_native_handle() const;
void enable_keylog();
private:
WOLFSSL_CTX *ssl_ctx_;
};
#endif // !defined(TLS_SERVER_CONTEXT_WOLFSSL_H)

View File

@ -0,0 +1,56 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SERVER_SESSION_H
#define TLS_SERVER_SESSION_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#ifdef WITH_EXAMPLE_QUICTLS
# include "tls_server_session_quictls.h"
#endif // defined(WITH_EXAMPLE_QUICTLS)
#ifdef WITH_EXAMPLE_GNUTLS
# include "tls_server_session_gnutls.h"
#endif // defined(WITH_EXAMPLE_GNUTLS)
#ifdef WITH_EXAMPLE_BORINGSSL
# include "tls_server_session_boringssl.h"
#endif // defined(WITH_EXAMPLE_BORINGSSL)
#ifdef WITH_EXAMPLE_PICOTLS
# include "tls_server_session_picotls.h"
#endif // defined(WITH_EXAMPLE_PICOTLS)
#ifdef WITH_EXAMPLE_WOLFSSL
# include "tls_server_session_wolfssl.h"
#endif // defined(WITH_EXAMPLE_WOLFSSL)
#ifdef WITH_EXAMPLE_OSSL
# include "tls_server_session_ossl.h"
#endif // defined(WITH_EXAMPLE_OSSL)
#endif // !defined(TLS_SERVER_SESSION_H)

View File

@ -0,0 +1,84 @@
/*
* ngtcp2
*
* Copyright (c) 2021 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_server_session_boringssl.h"
#include <cassert>
#include <iostream>
#include <ngtcp2/ngtcp2.h>
#include "tls_server_context_boringssl.h"
#include "server_base.h"
extern Config config;
TLSServerSession::TLSServerSession() {}
TLSServerSession::~TLSServerSession() {}
int TLSServerSession::init(const TLSServerContext &tls_ctx,
HandlerBase *handler) {
auto ssl_ctx = tls_ctx.get_native_handle();
ssl_ = SSL_new(ssl_ctx);
if (!ssl_) {
std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
SSL_set_app_data(ssl_, handler->conn_ref());
SSL_set_accept_state(ssl_);
SSL_set_early_data_enabled(ssl_, 1);
std::array<uint8_t, 128> quic_early_data_ctx;
ngtcp2_transport_params params;
ngtcp2_transport_params_default(&params);
params.initial_max_streams_bidi = config.max_streams_bidi;
params.initial_max_streams_uni = config.max_streams_uni;
params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local;
params.initial_max_stream_data_bidi_remote =
config.max_stream_data_bidi_remote;
params.initial_max_stream_data_uni = config.max_stream_data_uni;
params.initial_max_data = config.max_data;
auto quic_early_data_ctxlen = ngtcp2_transport_params_encode(
quic_early_data_ctx.data(), quic_early_data_ctx.size(), &params);
if (quic_early_data_ctxlen < 0) {
std::cerr << "ngtcp2_transport_params_encode: "
<< ngtcp2_strerror(static_cast<int>(quic_early_data_ctxlen))
<< std::endl;
return -1;
}
if (SSL_set_quic_early_data_context(ssl_, quic_early_data_ctx.data(),
as_unsigned(quic_early_data_ctxlen)) !=
1) {
std::cerr << "SSL_set_quic_early_data_context failed" << std::endl;
return -1;
}
return 0;
}

View File

@ -0,0 +1,47 @@
/*
* ngtcp2
*
* Copyright (c) 2021 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SERVER_SESSION_BORINGSSL_H
#define TLS_SERVER_SESSION_BORINGSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include "tls_session_base_quictls.h"
class TLSServerContext;
class HandlerBase;
class TLSServerSession : public TLSSessionBase {
public:
TLSServerSession();
~TLSServerSession();
int init(const TLSServerContext &tls_ctx, HandlerBase *handler);
// ticket is sent automatically.
int send_session_ticket() { return 0; }
};
#endif // !defined(TLS_SERVER_SESSION_BORINGSSL_H)

View File

@ -0,0 +1,62 @@
/*
* ngtcp2
*
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_server_session_ossl.h"
#include <iostream>
#include <openssl/err.h>
#include "tls_server_context_ossl.h"
#include "server_base.h"
TLSServerSession::TLSServerSession() {}
TLSServerSession::~TLSServerSession() {}
int TLSServerSession::init(const TLSServerContext &tls_ctx,
HandlerBase *handler) {
auto ssl_ctx = tls_ctx.get_native_handle();
auto ssl = SSL_new(ssl_ctx);
if (!ssl) {
std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
ngtcp2_crypto_ossl_ctx_set_ssl(ossl_ctx_, ssl);
if (ngtcp2_crypto_ossl_configure_server_session(ssl) != 0) {
std::cerr << "ngtcp2_crypto_ossl_configure_server_session failed"
<< std::endl;
return -1;
}
SSL_set_app_data(ssl, handler->conn_ref());
SSL_set_accept_state(ssl);
SSL_set_quic_tls_early_data_enabled(ssl, 1);
return 0;
}

View File

@ -0,0 +1,47 @@
/*
* ngtcp2
*
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SERVER_SESSION_OSSL_H
#define TLS_SERVER_SESSION_OSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include "tls_session_base_ossl.h"
class TLSServerContext;
class HandlerBase;
class TLSServerSession : public TLSSessionBase {
public:
TLSServerSession();
~TLSServerSession();
int init(const TLSServerContext &tls_ctx, HandlerBase *handler);
// ticket is sent automatically.
int send_session_ticket() { return 0; }
};
#endif // !defined(TLS_SERVER_SESSION_OSSL_H)

View File

@ -0,0 +1,70 @@
/*
* ngtcp2
*
* Copyright (c) 2022 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_server_session_picotls.h"
#include <cassert>
#include <iostream>
#include <ngtcp2/ngtcp2_crypto_picotls.h>
#include "tls_server_context_picotls.h"
#include "server_base.h"
#include "util.h"
using namespace ngtcp2;
extern Config config;
TLSServerSession::TLSServerSession() {}
TLSServerSession::~TLSServerSession() {}
int TLSServerSession::init(TLSServerContext &tls_ctx, HandlerBase *handler) {
cptls_.ptls = ptls_server_new(tls_ctx.get_native_handle());
if (!cptls_.ptls) {
std::cerr << "ptls_server_new failed" << std::endl;
return -1;
}
*ptls_get_data_ptr(cptls_.ptls) = handler->conn_ref();
cptls_.handshake_properties.additional_extensions =
new ptls_raw_extension_t[2]{
{
.type = UINT16_MAX,
},
{
.type = UINT16_MAX,
},
};
if (ngtcp2_crypto_picotls_configure_server_session(&cptls_) != 0) {
std::cerr << "ngtcp2_crypto_picotls_configure_server_session failed"
<< std::endl;
return -1;
}
return 0;
}

View File

@ -0,0 +1,47 @@
/*
* ngtcp2
*
* Copyright (c) 2022 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SERVER_SESSION_PICOTLS_H
#define TLS_SERVER_SESSION_PICOTLS_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include "tls_session_base_picotls.h"
class TLSServerContext;
class HandlerBase;
class TLSServerSession : public TLSSessionBase {
public:
TLSServerSession();
~TLSServerSession();
int init(TLSServerContext &tls_ctx, HandlerBase *handler);
// ticket is sent automatically.
int send_session_ticket() { return 0; }
};
#endif // !defined(TLS_SERVER_SESSION_PICOTLS_H)

View File

@ -0,0 +1,56 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_server_session_quictls.h"
#include <iostream>
#include <openssl/err.h>
#include "tls_server_context_quictls.h"
#include "server_base.h"
TLSServerSession::TLSServerSession() {}
TLSServerSession::~TLSServerSession() {}
int TLSServerSession::init(const TLSServerContext &tls_ctx,
HandlerBase *handler) {
auto ssl_ctx = tls_ctx.get_native_handle();
ssl_ = SSL_new(ssl_ctx);
if (!ssl_) {
std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
SSL_set_app_data(ssl_, handler->conn_ref());
SSL_set_accept_state(ssl_);
#ifndef LIBRESSL_VERSION_NUMBER
SSL_set_quic_early_data_enabled(ssl_, 1);
#endif // !defined(LIBRESSL_VERSION_NUMBER)
return 0;
}

View File

@ -0,0 +1,47 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SERVER_SESSION_QUICTLS_H
#define TLS_SERVER_SESSION_QUICTLS_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include "tls_session_base_quictls.h"
class TLSServerContext;
class HandlerBase;
class TLSServerSession : public TLSSessionBase {
public:
TLSServerSession();
~TLSServerSession();
int init(const TLSServerContext &tls_ctx, HandlerBase *handler);
// ticket is sent automatically.
int send_session_ticket() { return 0; }
};
#endif // !defined(TLS_SERVER_SESSION_QUICTLS_H)

View File

@ -0,0 +1,57 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_server_session_wolfssl.h"
#include <iostream>
#include "tls_server_context_wolfssl.h"
#include "server_base.h"
TLSServerSession::TLSServerSession() {}
TLSServerSession::~TLSServerSession() {}
int TLSServerSession::init(const TLSServerContext &tls_ctx,
HandlerBase *handler) {
auto ssl_ctx = tls_ctx.get_native_handle();
ssl_ = wolfSSL_new(ssl_ctx);
if (!ssl_) {
std::cerr << "wolfSSL_new: "
<< wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr)
<< std::endl;
return -1;
}
wolfSSL_set_app_data(ssl_, handler->conn_ref());
wolfSSL_set_accept_state(ssl_);
#ifdef WOLFSSL_EARLY_DATA
wolfSSL_set_quic_early_data_enabled(ssl_, 1);
#endif // defined(WOLFSSL_EARLY_DATA)
// Just use QUIC v1
wolfSSL_set_quic_transport_version(ssl_, 0x39);
return 0;
}

View File

@ -0,0 +1,47 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SERVER_SESSION_WOLFSSL_H
#define TLS_SERVER_SESSION_WOLFSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include "tls_session_base_wolfssl.h"
class TLSServerContext;
class HandlerBase;
class TLSServerSession : public TLSSessionBase {
public:
TLSServerSession();
~TLSServerSession();
int init(const TLSServerContext &tls_ctx, HandlerBase *handler);
// ticket is sent automatically.
int send_session_ticket() { return 0; }
};
#endif // !defined(TLS_SERVER_SESSION_WOLFSSL_H)

View File

@ -0,0 +1,77 @@
/*
* ngtcp2
*
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_session_base_ossl.h"
#include <array>
#include "util.h"
#include "template.h"
using namespace ngtcp2;
using namespace std::literals;
TLSSessionBase::TLSSessionBase() {
ngtcp2_crypto_ossl_ctx_new(&ossl_ctx_, NULL);
}
TLSSessionBase::~TLSSessionBase() {
auto ssl = ngtcp2_crypto_ossl_ctx_get_ssl(ossl_ctx_);
if (ssl) {
SSL_set_app_data(ssl, NULL);
SSL_free(ssl);
}
ngtcp2_crypto_ossl_ctx_del(ossl_ctx_);
}
ngtcp2_crypto_ossl_ctx *TLSSessionBase::get_native_handle() const {
return ossl_ctx_;
}
std::string TLSSessionBase::get_cipher_name() const {
return SSL_get_cipher_name(ngtcp2_crypto_ossl_ctx_get_ssl(ossl_ctx_));
}
std::string_view TLSSessionBase::get_negotiated_group() const {
auto ssl = ngtcp2_crypto_ossl_ctx_get_ssl(ossl_ctx_);
auto name = SSL_get0_group_name(ssl);
if (!name) {
return ""sv;
}
return name;
}
std::string TLSSessionBase::get_selected_alpn() const {
auto ssl = ngtcp2_crypto_ossl_ctx_get_ssl(ossl_ctx_);
const unsigned char *alpn = nullptr;
unsigned int alpnlen;
SSL_get0_alpn_selected(ssl, &alpn, &alpnlen);
return std::string{alpn, alpn + alpnlen};
}

View File

@ -0,0 +1,56 @@
/*
* ngtcp2
*
* Copyright (c) 2025 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SESSION_BASE_OSSL_H
#define TLS_SESSION_BASE_OSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <string>
#include <string_view>
#include <ngtcp2/ngtcp2_crypto_ossl.h>
#include <openssl/ssl.h>
class TLSSessionBase {
public:
TLSSessionBase();
~TLSSessionBase();
ngtcp2_crypto_ossl_ctx *get_native_handle() const;
std::string get_cipher_name() const;
std::string_view get_negotiated_group() const;
std::string get_selected_alpn() const;
// Keylog is enabled per SSL_CTX.
void enable_keylog() {}
protected:
ngtcp2_crypto_ossl_ctx *ossl_ctx_;
};
#endif // !defined(TLS_SESSION_BASE_OSSL_H)

View File

@ -0,0 +1,56 @@
/*
* ngtcp2
*
* Copyright (c) 2022 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_session_base_picotls.h"
TLSSessionBase::TLSSessionBase() { ngtcp2_crypto_picotls_ctx_init(&cptls_); }
TLSSessionBase::~TLSSessionBase() {
ngtcp2_crypto_picotls_deconfigure_session(&cptls_);
delete[] cptls_.handshake_properties.additional_extensions;
if (cptls_.ptls) {
ptls_free(cptls_.ptls);
}
}
ngtcp2_crypto_picotls_ctx *TLSSessionBase::get_native_handle() {
return &cptls_;
}
std::string TLSSessionBase::get_cipher_name() const {
auto cs = ptls_get_cipher(cptls_.ptls);
return cs->aead->name;
}
std::string TLSSessionBase::get_selected_alpn() const {
auto alpn = ptls_get_negotiated_protocol(cptls_.ptls);
if (!alpn) {
return {};
}
return alpn;
}

View File

@ -0,0 +1,60 @@
/*
* ngtcp2
*
* Copyright (c) 2022 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SESSION_BASE_PICOTLS_H
#define TLS_SESSION_BASE_PICOTLS_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <string>
#include <string_view>
#include <ngtcp2/ngtcp2_crypto_picotls.h>
#include <picotls.h>
class TLSSessionBase {
public:
TLSSessionBase();
~TLSSessionBase();
ngtcp2_crypto_picotls_ctx *get_native_handle();
std::string get_cipher_name() const;
std::string_view get_negotiated_group() const {
using namespace std::literals;
return ""sv;
}
std::string get_selected_alpn() const;
// TODO make keylog work with picotls
void enable_keylog() {}
protected:
ngtcp2_crypto_picotls_ctx cptls_;
};
#endif // !defined(TLS_SESSION_BASE_PICOTLS_H)

View File

@ -0,0 +1,102 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_session_base_quictls.h"
#include <array>
#include "util.h"
#include "template.h"
using namespace ngtcp2;
using namespace std::literals;
TLSSessionBase::TLSSessionBase() : ssl_{nullptr} {}
TLSSessionBase::~TLSSessionBase() {
if (ssl_) {
SSL_free(ssl_);
}
}
SSL *TLSSessionBase::get_native_handle() const { return ssl_; }
std::string TLSSessionBase::get_cipher_name() const {
return SSL_get_cipher_name(ssl_);
}
std::string_view TLSSessionBase::get_negotiated_group() const {
#ifdef WITH_EXAMPLE_BORINGSSL
return SSL_get_group_name(SSL_get_group_id(ssl_));
#elif OPENSSL_VERSION_NUMBER >= 0x30000000L
auto name =
SSL_group_to_name(ssl_, static_cast<int>(SSL_get_negotiated_group(ssl_)));
if (!name) {
return ""sv;
}
return name;
#elif defined(LIBRESSL_VERSION_NUMBER)
return ""sv;
#else // !(defined(WITH_EXAMPLE_BORINGSSL) ||
// OPENSSL_VERSION_NUMBER >= 0x30000000L ||
// defined(LIBRESSL_VERSION_NUMBER))
EVP_PKEY *key;
if (!SSL_get_tmp_key(ssl_, &key)) {
return ""sv;
}
auto key_del = defer(EVP_PKEY_free, key);
auto nid = EVP_PKEY_id(key);
if (nid == EVP_PKEY_EC) {
auto ec = EVP_PKEY_get1_EC_KEY(key);
auto ec_del = defer(EC_KEY_free, ec);
nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
}
auto name = EC_curve_nid2nist(nid);
if (!name) {
name = OBJ_nid2sn(nid);
if (!name) {
return ""sv;
}
}
return name;
#endif // !(defined(WITH_EXAMPLE_BORINGSSL) ||
// OPENSSL_VERSION_NUMBER >= 0x30000000L ||
// defined(LIBRESSL_VERSION_NUMBER))
}
std::string TLSSessionBase::get_selected_alpn() const {
const unsigned char *alpn = nullptr;
unsigned int alpnlen;
SSL_get0_alpn_selected(ssl_, &alpn, &alpnlen);
return std::string{alpn, alpn + alpnlen};
}

View File

@ -0,0 +1,54 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SESSION_BASE_QUICTLS_H
#define TLS_SESSION_BASE_QUICTLS_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <string>
#include <string_view>
#include <openssl/ssl.h>
class TLSSessionBase {
public:
TLSSessionBase();
~TLSSessionBase();
SSL *get_native_handle() const;
std::string get_cipher_name() const;
std::string_view get_negotiated_group() const;
std::string get_selected_alpn() const;
// Keylog is enabled per SSL_CTX.
void enable_keylog() {}
protected:
SSL *ssl_;
};
#endif // !defined(TLS_SESSION_BASE_QUICTLS_H)

View File

@ -0,0 +1,54 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_session_base_wolfssl.h"
#include <array>
#include "util.h"
using namespace ngtcp2;
TLSSessionBase::TLSSessionBase() : ssl_{nullptr} {}
TLSSessionBase::~TLSSessionBase() {
if (ssl_) {
wolfSSL_free(ssl_);
}
}
WOLFSSL *TLSSessionBase::get_native_handle() const { return ssl_; }
std::string TLSSessionBase::get_cipher_name() const {
return wolfSSL_get_cipher_name(ssl_);
}
std::string TLSSessionBase::get_selected_alpn() const {
char *alpn = nullptr;
unsigned short alpnlen;
wolfSSL_ALPN_GetProtocol(ssl_, &alpn, &alpnlen);
return std::string{alpn, alpn + alpnlen};
}

View File

@ -0,0 +1,60 @@
/*
* ngtcp2
*
* Copyright (c) 2020 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SESSION_BASE_WOLFSSL_H
#define TLS_SESSION_BASE_WOLFSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <string>
#include <string_view>
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#include <wolfssl/quic.h>
class TLSSessionBase {
public:
TLSSessionBase();
~TLSSessionBase();
WOLFSSL *get_native_handle() const;
std::string get_cipher_name() const;
std::string_view get_negotiated_group() const {
using namespace std::literals;
return ""sv;
}
std::string get_selected_alpn() const;
// Keylog is enabled per SSL_CTX.
void enable_keylog() {}
protected:
WOLFSSL *ssl_;
};
#endif // !defined(TLS_SESSION_BASE_WOLFSSL_H)

View File

@ -0,0 +1,89 @@
/*
* ngtcp2
*
* Copyright (c) 2024 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_shared_boringssl.h"
#ifdef HAVE_LIBBROTLI
# include <brotli/encode.h>
# include <brotli/decode.h>
#endif // defined(HAVE_LIBBROTLI)
namespace ngtcp2 {
namespace tls {
#ifdef HAVE_LIBBROTLI
int cert_compress(SSL *ssl, CBB *out, const uint8_t *in, size_t in_len) {
uint8_t *dest;
auto compressed_size = BrotliEncoderMaxCompressedSize(in_len);
if (compressed_size == 0) {
return 0;
}
if (!CBB_reserve(out, &dest, compressed_size)) {
return 0;
}
if (BrotliEncoderCompress(BROTLI_MAX_QUALITY, BROTLI_DEFAULT_WINDOW,
BROTLI_MODE_GENERIC, in_len, in, &compressed_size,
dest) != BROTLI_TRUE) {
return 0;
}
if (!CBB_did_write(out, compressed_size)) {
return 0;
}
return 1;
}
int cert_decompress(SSL *ssl, CRYPTO_BUFFER **out, size_t uncompressed_len,
const uint8_t *in, size_t in_len) {
uint8_t *dest;
auto buf = CRYPTO_BUFFER_alloc(&dest, uncompressed_len);
auto len = uncompressed_len;
if (BrotliDecoderDecompress(in_len, in, &len, dest) !=
BROTLI_DECODER_RESULT_SUCCESS) {
CRYPTO_BUFFER_free(buf);
return 0;
}
if (uncompressed_len != len) {
CRYPTO_BUFFER_free(buf);
return 0;
}
*out = buf;
return 1;
}
#endif // defined(HAVE_LIBBROTLI)
} // namespace tls
} // namespace ngtcp2

View File

@ -0,0 +1,51 @@
/*
* ngtcp2
*
* Copyright (c) 2024 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef TLS_SHARED_BORINGSSL_H
#define TLS_SHARED_BORINGSSL_H
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif // defined(HAVE_CONFIG_H)
#include <openssl/ssl.h>
namespace ngtcp2 {
namespace tls {
constexpr uint16_t CERTIFICATE_COMPRESSION_ALGO_BROTLI = 2;
#ifdef HAVE_LIBBROTLI
int cert_compress(SSL *ssl, CBB *out, const uint8_t *in, size_t in_len);
int cert_decompress(SSL *ssl, CRYPTO_BUFFER **out, size_t uncompressed_len,
const uint8_t *in, size_t in_len);
#endif // defined(HAVE_LIBBROTLI)
} // namespace tls
} // namespace ngtcp2
#endif // !defined(TLS_SHARED_BORINGSSL_H)

View File

@ -0,0 +1,59 @@
/*
* ngtcp2
*
* Copyright (c) 2023 ngtcp2 contributors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "tls_shared_picotls.h"
#include <cstdio>
#include <cstdarg>
#include <fstream>
extern std::ofstream keylog_file;
namespace {
void log_event_cb(ptls_log_event_t *self, ptls_t *ptls, const char *type,
const char *fmt, ...) {
char buf[128];
va_list ap;
va_start(ap, fmt);
auto len = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (len < 0 || static_cast<size_t>(len) >= sizeof(buf)) {
return;
}
char randhex[PTLS_HELLO_RANDOM_SIZE * 2 + 1];
ptls_hexdump(randhex, ptls_get_client_random(ptls).base,
PTLS_HELLO_RANDOM_SIZE);
keylog_file << type << ' ' << randhex << ' ';
keylog_file.write(buf, len);
keylog_file << '\n';
keylog_file.flush();
}
} // namespace
ptls_log_event_t log_event = {log_event_cb};

Some files were not shown because too many files have changed in this diff Show More