libknot: extend TLS API to be used for non-DNS communication

This commit is contained in:
Daniel Salzman 2025-03-08 20:40:34 +01:00
parent c7b11a74c9
commit fb9b2cc102
10 changed files with 92 additions and 61 deletions

View file

@ -201,8 +201,8 @@ libknot.so.15 libknot15 #MINVER#
knot_tls_pin@Base 3.4.0
knot_tls_pin_check@Base 3.4.0
knot_tls_priority@Base 3.4.7
knot_tls_recv_dns@Base 3.4.0
knot_tls_send_dns@Base 3.4.0
knot_tls_recv@Base 3.5.0
knot_tls_send@Base 3.5.0
knot_tls_session@Base 3.4.0
knot_tls_session_available@Base 3.4.1
knot_tls_session_load@Base 3.4.1

View file

@ -382,8 +382,8 @@ static void send_update_response(conf_t *conf, zone_t *zone, knot_request_t *req
}
if (net_is_stream(req->fd) && req->tls_req_ctx.conn != NULL) {
(void)knot_tls_send_dns(req->tls_req_ctx.conn,
req->resp->wire, req->resp->size);
(void)knot_tls_send(req->tls_req_ctx.conn,
req->resp->wire, req->resp->size);
knot_tls_conn_block(req->tls_req_ctx.conn, false);
}
#ifdef ENABLE_QUIC

View file

@ -134,7 +134,7 @@ static int request_send(knot_request_t *request, int timeout_ms, bool *reused_fd
/* Send query. */
if (use_tls(request)) {
ret = knot_tls_send_dns(request->tls_req_ctx.conn, wire, wire_len);
ret = knot_tls_send(request->tls_req_ctx.conn, wire, wire_len);
knot_tls_req_ctx_maint(&request->tls_req_ctx, request);
} else if (use_quic(request)) {
#ifdef ENABLE_QUIC
@ -175,7 +175,7 @@ static int request_recv(knot_request_t *request, int timeout_ms)
/* Receive it */
if (use_tls(request)) {
ret = knot_tls_recv_dns(request->tls_req_ctx.conn, resp->wire, resp->max_size);
ret = knot_tls_recv(request->tls_req_ctx.conn, resp->wire, resp->max_size);
knot_tls_req_ctx_maint(&request->tls_req_ctx, request);
} else if (use_quic(request)) {
#ifdef ENABLE_QUIC

View file

@ -24,7 +24,8 @@ int knot_tls_req_ctx_init(knot_tls_req_ctx_t *ctx, int fd,
}
// Use HS = 4x IO timeout, as the RMT IO timeout is usually high.
ctx->ctx = knot_tls_ctx_new(creds, io_timeout_ms, 4 * io_timeout_ms, false);
ctx->ctx = knot_tls_ctx_new(creds, io_timeout_ms, 4 * io_timeout_ms,
KNOT_TLS_CLIENT | KNOT_TLS_DNS | KNOT_TLS_EARLY_DATA);
if (ctx->ctx == NULL) {
knot_creds_free(creds);
return KNOT_ENOMEM;

View file

@ -172,7 +172,7 @@ static int tcp_handle(tcp_context_t *tcp, knotd_qdata_params_t *params,
case KNOT_NET_EAGAIN: // Unfinished handshake, continue later.
return KNOT_EOK;
case KNOT_EOK: // Finished handshake, continue with receiving message.
recv = knot_tls_recv_dns(params->tls_conn, rx->iov_base, rx->iov_len);
recv = knot_tls_recv(params->tls_conn, rx->iov_base, rx->iov_len);
break;
default: // E.g. handshake timeout.
assert(ret < 0);
@ -199,7 +199,7 @@ static int tcp_handle(tcp_context_t *tcp, knotd_qdata_params_t *params,
if (ans->size > 0 && send_state(tcp->layer.state)) {
int sent;
if (params->tls_conn != NULL) {
sent = knot_tls_send_dns(params->tls_conn, ans->wire, ans->size);
sent = knot_tls_send(params->tls_conn, ans->wire, ans->size);
} else {
sent = net_dns_tcp_send(params->socket, ans->wire, ans->size,
tcp->io_timeout, NULL);
@ -424,7 +424,8 @@ int tcp_master(dthread_t *thread)
if (tls) {
// Set the HS timeout to 8x the RMT IO one as the HS duration can be up to 4*roundtrip.
tcp.tls_ctx = knot_tls_ctx_new(handler->server->quic_creds,
tcp.io_timeout, 8 * tcp.io_timeout, true);
tcp.io_timeout, 8 * tcp.io_timeout,
KNOT_TLS_SERVER | KNOT_TLS_DNS | KNOT_TLS_EARLY_DATA);
if (tcp.tls_ctx == NULL) {
ret = KNOT_ENOMEM;
goto finish;

View file

@ -132,8 +132,9 @@ static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref)
static int tls_init_conn_session(knot_quic_conn_t *conn, bool server)
{
int ret = knot_tls_session(&conn->tls_session, conn->quic_table->creds,
conn->quic_table->priority, true,
true, server);
conn->quic_table->priority,
(server ? KNOT_TLS_SERVER : KNOT_TLS_CLIENT) |
KNOT_TLS_QUIC | KNOT_TLS_DNS | KNOT_TLS_EARLY_DATA);
if (ret != KNOT_EOK) {
return TLS_CALLBACK_ERR;
}

View file

@ -29,7 +29,7 @@ typedef struct knot_tls_session {
_public_
knot_tls_ctx_t *knot_tls_ctx_new(struct knot_creds *creds, unsigned io_timeout,
unsigned hs_timeout, bool server)
unsigned hs_timeout, knot_tls_flag_t flags)
{
knot_tls_ctx_t *res = calloc(1, sizeof(*res));
if (res == NULL) {
@ -39,7 +39,7 @@ knot_tls_ctx_t *knot_tls_ctx_new(struct knot_creds *creds, unsigned io_timeout,
res->creds = creds;
res->handshake_timeout = hs_timeout;
res->io_timeout = io_timeout;
res->server = server;
res->flags = flags;
int ret = gnutls_priority_init2(&res->priority, knot_tls_priority(false), NULL,
GNUTLS_PRIORITY_INIT_DEF_APPEND);
@ -71,7 +71,7 @@ knot_tls_conn_t *knot_tls_conn_new(knot_tls_ctx_t *ctx, int sock_fd)
res->fd = sock_fd;
int ret = knot_tls_session(&res->session, ctx->creds, ctx->priority,
false, true, ctx->server);
ctx->flags);
if (ret != KNOT_EOK) {
goto fail;
}
@ -195,7 +195,8 @@ int knot_tls_handshake(knot_tls_conn_t *conn, bool oneshot)
*timeout_ptr = MAX(*timeout_ptr - running_ms, 0); \
}
static ssize_t recv_data(knot_tls_conn_t *conn, void *data, size_t size, int *timeout_ptr)
static ssize_t recv_data(knot_tls_conn_t *conn, void *data, size_t size,
int *timeout_ptr, bool oneshot)
{
gnutls_record_set_timeout(conn->session, *timeout_ptr);
@ -205,6 +206,9 @@ static ssize_t recv_data(knot_tls_conn_t *conn, void *data, size_t size, int *ti
TIMEOUT_CTX_INIT
res = gnutls_record_recv(conn->session, data + total, size - total);
if (res > 0) {
if (oneshot) {
return res;
}
total += res;
} else if (res == 0) {
return KNOT_ECONNRESET;
@ -220,7 +224,7 @@ static ssize_t recv_data(knot_tls_conn_t *conn, void *data, size_t size, int *ti
}
_public_
ssize_t knot_tls_recv_dns(knot_tls_conn_t *conn, void *data, size_t size)
ssize_t knot_tls_recv(knot_tls_conn_t *conn, void *data, size_t size)
{
if (conn == NULL || data == NULL) {
return KNOT_EINVAL;
@ -237,27 +241,31 @@ ssize_t knot_tls_recv_dns(knot_tls_conn_t *conn, void *data, size_t size)
int timeout = conn->ctx->io_timeout;
uint16_t msg_len;
ret = recv_data(conn, &msg_len, sizeof(msg_len), &timeout);
if (ret != sizeof(msg_len)) {
return ret;
}
if (conn->ctx->flags & KNOT_TLS_DNS) {
uint16_t msg_len;
ret = recv_data(conn, &msg_len, sizeof(msg_len), &timeout, false);
if (ret != sizeof(msg_len)) {
return ret;
}
msg_len = ntohs(msg_len);
if (size < msg_len) {
return KNOT_ESPACE;
}
msg_len = ntohs(msg_len);
if (size < msg_len) {
return KNOT_ESPACE;
}
ret = recv_data(conn, data, msg_len, &timeout);
if (ret != size) {
return ret;
}
ret = recv_data(conn, data, msg_len, &timeout, false);
if (ret != size) {
return ret;
}
return msg_len;
return msg_len;
} else {
return recv_data(conn, data, size, &timeout, true);
}
}
_public_
ssize_t knot_tls_send_dns(knot_tls_conn_t *conn, void *data, size_t size)
ssize_t knot_tls_send(knot_tls_conn_t *conn, void *data, size_t size)
{
if (conn == NULL || data == NULL || size > UINT16_MAX) {
return KNOT_EINVAL;
@ -271,10 +279,12 @@ ssize_t knot_tls_send_dns(knot_tls_conn_t *conn, void *data, size_t size)
// Enable data buffering.
gnutls_record_cork(conn->session);
uint16_t msg_len = htons(size);
res = gnutls_record_send(conn->session, &msg_len, sizeof(msg_len));
if (res != sizeof(msg_len)) {
return KNOT_NET_ESEND;
if (conn->ctx->flags & KNOT_TLS_DNS) {
uint16_t msg_len = htons(size);
res = gnutls_record_send(conn->session, &msg_len, sizeof(msg_len));
if (res != sizeof(msg_len)) {
return KNOT_NET_ESEND;
}
}
res = gnutls_record_send(conn->session, data, size);

View file

@ -18,6 +18,8 @@
#include <stdint.h>
#include <sys/types.h>
#include <libknot/quic/tls_common.h>
struct gnutls_priority_st;
typedef enum {
@ -30,9 +32,9 @@ typedef enum {
typedef struct knot_tls_ctx {
struct knot_creds *creds;
struct gnutls_priority_st *priority;
knot_tls_flag_t flags;
unsigned handshake_timeout;
unsigned io_timeout;
bool server;
} knot_tls_ctx_t;
typedef struct knot_tls_conn {
@ -49,12 +51,12 @@ typedef struct knot_tls_conn {
* \param creds Certificate credentials.
* \param io_timeout Connections' IO-timeout (in milliseconds).
* \param hs_timeout Handshake timeout (in milliseconds).
* \param server Server context (otherwise client).
* \param flags Specify client/server mode and common/dns format.
*
* \return Initialized context or NULL.
*/
knot_tls_ctx_t *knot_tls_ctx_new(struct knot_creds *creds, unsigned io_timeout,
unsigned hs_timeout, bool server);
unsigned hs_timeout, knot_tls_flag_t flags);
/*!
* \brief Free DoT answering context.
@ -118,20 +120,24 @@ int knot_tls_session_load(knot_tls_conn_t *conn, struct knot_tls_session *sessio
int knot_tls_handshake(knot_tls_conn_t *conn, bool oneshot);
/*!
* \brief Receive a size-word-prefixed DNS message.
* \brief Receive a data blob.
*
* \note In the DNS mode, the two-byte-size prefix is stripped upon reception,
* not stored to the buffer.
*
* \param conn DoT connection.
* \param data Destination buffer.
* \param size Maximum buffer size.
*
* \return Either the DNS message size received or negative error code.
*
* \note The two-byte-size-prefix is stripped upon reception, not stored to the buffer.
*/
ssize_t knot_tls_recv_dns(knot_tls_conn_t *conn, void *data, size_t size);
ssize_t knot_tls_recv(knot_tls_conn_t *conn, void *data, size_t size);
/*!
* \brief Send a size-word-prefixed DNS message.
* \brief Send a data blob.
*
* \note In the DNS mode, the two-byte-size prefix is sended before the data
* blob itself.
*
* \param conn DoT connection.
* \param data DNS payload.
@ -139,7 +145,7 @@ ssize_t knot_tls_recv_dns(knot_tls_conn_t *conn, void *data, size_t size);
*
* \return Either exactly 'size' or a negative error code.
*/
ssize_t knot_tls_send_dns(knot_tls_conn_t *conn, void *data, size_t size);
ssize_t knot_tls_send(knot_tls_conn_t *conn, void *data, size_t size);
/*!
* \brief Set or unset the conection's BLOCKED flag.

View file

@ -370,26 +370,32 @@ _public_
int knot_tls_session(struct gnutls_session_int **session,
struct knot_creds *creds,
struct gnutls_priority_st *priority,
bool quic,
bool early_data,
bool server)
knot_tls_flag_t flags)
{
if (session == NULL || creds == NULL || priority == NULL) {
return KNOT_EINVAL;
}
const char *alpn = quic ? "\x03""doq" : "\x03""dot";
gnutls_init_flags_t flags = GNUTLS_NO_SIGNAL;
bool server = flags & KNOT_TLS_SERVER;
bool quic = flags & KNOT_TLS_QUIC;
bool early_data = flags & KNOT_TLS_EARLY_DATA;
const char *alpn = NULL;
if (flags & KNOT_TLS_DNS) {
alpn = quic ? "\x03""doq" : "\x03""dot";
}
gnutls_init_flags_t tls_flags = GNUTLS_NO_SIGNAL;
if (early_data) {
flags |= GNUTLS_ENABLE_EARLY_DATA;
tls_flags |= GNUTLS_ENABLE_EARLY_DATA;
#ifdef ENABLE_QUIC // Next flags aren't available in older GnuTLS versions.
if (quic) {
flags |= GNUTLS_NO_END_OF_EARLY_DATA;
tls_flags |= GNUTLS_NO_END_OF_EARLY_DATA;
}
#endif
}
int ret = gnutls_init(session, (server ? GNUTLS_SERVER : GNUTLS_CLIENT) | flags);
int ret = gnutls_init(session, (server ? GNUTLS_SERVER : GNUTLS_CLIENT) | tls_flags);
if (ret == GNUTLS_E_SUCCESS) {
gnutls_certificate_send_x509_rdn_sequence(*session, 1);
gnutls_certificate_server_set_request(*session, GNUTLS_CERT_REQUEST);
@ -399,8 +405,10 @@ int knot_tls_session(struct gnutls_session_int **session,
ret = gnutls_session_ticket_enable_server(*session, &creds->tls_ticket_key);
}
if (ret == GNUTLS_E_SUCCESS) {
const gnutls_datum_t alpn_datum = { (void *)&alpn[1], alpn[0] };
gnutls_alpn_set_protocols(*session, &alpn_datum, 1, GNUTLS_ALPN_MANDATORY);
if (alpn != NULL) {
const gnutls_datum_t alpn_datum = { (void *)&alpn[1], alpn[0] };
gnutls_alpn_set_protocols(*session, &alpn_datum, 1, GNUTLS_ALPN_MANDATORY);
}
if (early_data) {
gnutls_record_set_max_early_data_size(*session, 0xffffffffu);
}

View file

@ -26,6 +26,14 @@ struct gnutls_x509_crt_int;
struct knot_creds;
struct knot_tls_session;
typedef enum {
KNOT_TLS_CLIENT = 0,
KNOT_TLS_SERVER = (1 << 0),
KNOT_TLS_QUIC = (1 << 1),
KNOT_TLS_DNS = (1 << 2),
KNOT_TLS_EARLY_DATA = (1 << 3),
} knot_tls_flag_t;
/*!
* \brief Get priority string for GnuTLS priority initialization.
*
@ -96,18 +104,14 @@ void knot_creds_free(struct knot_creds *creds);
* \param session Out: initialized GnuTLS session struct.
* \param creds Certificate credentials.
* \param priority Session priority configuration.
* \param quic Session is for ngtcp2/QUIC (otherwise TLS).
* \param early_data Allow early data.
* \param server Should be server session (otherwise client).
* \param flags TLS-related flags.
*
* \return KNOT_E*
*/
int knot_tls_session(struct gnutls_session_int **session,
struct knot_creds *creds,
struct gnutls_priority_st *priority,
bool quic,
bool early_data,
bool server);
knot_tls_flag_t flags);
/*!
* \brief Gets local or remote certificate pin.