diff options
Diffstat (limited to 'src/libknot/quic/quic.c')
-rw-r--r-- | src/libknot/quic/quic.c | 343 |
1 files changed, 20 insertions, 323 deletions
diff --git a/src/libknot/quic/quic.c b/src/libknot/quic/quic.c index f9d1d1d..4eb84c3 100644 --- a/src/libknot/quic/quic.c +++ b/src/libknot/quic/quic.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,7 +33,6 @@ #include "contrib/macros.h" #include "contrib/sockaddr.h" -#include "contrib/string.h" #include "contrib/ucw/lists.h" #include "libknot/endian.h" #include "libdnssec/error.h" @@ -58,19 +57,6 @@ #define TLS_CALLBACK_ERR (-1) -const gnutls_datum_t doq_alpn = { - (unsigned char *)"doq", 3 -}; - -typedef struct knot_quic_creds { - gnutls_certificate_credentials_t tls_cert; - gnutls_anti_replay_t tls_anti_replay; - gnutls_datum_t tls_ticket_key; - bool peer; - uint8_t peer_pin_len; - uint8_t peer_pin[]; -} knot_quic_creds_t; - typedef struct knot_quic_session { node_t n; gnutls_datum_t tls_session; @@ -153,223 +139,6 @@ session_free: return ret; } -static int tls_anti_replay_db_add_func(void *dbf, time_t exp_time, - const gnutls_datum_t *key, - const gnutls_datum_t *data) -{ - return 0; -} - -static void tls_session_ticket_key_free(gnutls_datum_t *ticket) -{ - gnutls_memset(ticket->data, 0, ticket->size); - gnutls_free(ticket->data); -} - -static int self_key(gnutls_x509_privkey_t *privkey, const char *key_file) -{ - gnutls_datum_t data = { 0 }; - - int ret = gnutls_x509_privkey_init(privkey); - if (ret != GNUTLS_E_SUCCESS) { - return ret; - } - - int fd = open(key_file, O_RDONLY); - if (fd != -1) { - struct stat stat; - if (fstat(fd, &stat) != 0 || - (data.data = gnutls_malloc(stat.st_size)) == NULL || - read(fd, data.data, stat.st_size) != stat.st_size) { - ret = GNUTLS_E_KEYFILE_ERROR; - goto finish; - } - - data.size = stat.st_size; - ret = gnutls_x509_privkey_import_pkcs8(*privkey, &data, GNUTLS_X509_FMT_PEM, - NULL, GNUTLS_PKCS_PLAIN); - if (ret != GNUTLS_E_SUCCESS) { - goto finish; - } - } else { - ret = gnutls_x509_privkey_generate(*privkey, GNUTLS_PK_EDDSA_ED25519, - GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_ED25519), 0); - if (ret != GNUTLS_E_SUCCESS) { - goto finish; - } - - ret = gnutls_x509_privkey_export2_pkcs8(*privkey, GNUTLS_X509_FMT_PEM, NULL, - GNUTLS_PKCS_PLAIN, &data); - if (ret != GNUTLS_E_SUCCESS || - (fd = open(key_file, O_WRONLY | O_CREAT, 0600)) == -1 || - write(fd, data.data, data.size) != data.size) { - ret = GNUTLS_E_KEYFILE_ERROR; - goto finish; - } - } - -finish: - close(fd); - gnutls_free(data.data); - if (ret != GNUTLS_E_SUCCESS) { - gnutls_x509_privkey_deinit(*privkey); - *privkey = NULL; - } - return ret; -} - -static int self_signed_cert(gnutls_certificate_credentials_t tls_cert, - const char *key_file) -{ - gnutls_x509_privkey_t privkey = NULL; - gnutls_x509_crt_t cert = NULL; - - char *hostname = sockaddr_hostname(); - if (hostname == NULL) { - return GNUTLS_E_MEMORY_ERROR; - } - - int ret; - uint8_t serial[16]; - gnutls_rnd(GNUTLS_RND_NONCE, serial, sizeof(serial)); - // Clear the left-most bit to be a positive number (two's complement form). - serial[0] &= 0x7F; - -#define CHK(cmd) if ((ret = (cmd)) != GNUTLS_E_SUCCESS) { goto finish; } -#define NOW_DAYS(days) (time(NULL) + 24 * 3600 * (days)) - - CHK(self_key(&privkey, key_file)); - - CHK(gnutls_x509_crt_init(&cert)); - CHK(gnutls_x509_crt_set_version(cert, 3)); - CHK(gnutls_x509_crt_set_serial(cert, serial, sizeof(serial))); - CHK(gnutls_x509_crt_set_activation_time(cert, NOW_DAYS(-1))); - CHK(gnutls_x509_crt_set_expiration_time(cert, NOW_DAYS(10 * 365))); - CHK(gnutls_x509_crt_set_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, - hostname, strlen(hostname))); - CHK(gnutls_x509_crt_set_key(cert, privkey)); - CHK(gnutls_x509_crt_sign2(cert, cert, privkey, GNUTLS_DIG_SHA512, 0)); - - ret = gnutls_certificate_set_x509_key(tls_cert, &cert, 1, privkey); - -finish: - free(hostname); - gnutls_x509_crt_deinit(cert); - gnutls_x509_privkey_deinit(privkey); - - return ret; -} - -_public_ -struct knot_quic_creds *knot_quic_init_creds(const char *cert_file, - const char *key_file) -{ - knot_quic_creds_t *creds = calloc(1, sizeof(*creds)); - if (creds == NULL) { - return NULL; - } - - int ret = gnutls_certificate_allocate_credentials(&creds->tls_cert); - if (ret != GNUTLS_E_SUCCESS) { - goto fail; - } - - ret = gnutls_anti_replay_init(&creds->tls_anti_replay); - if (ret != GNUTLS_E_SUCCESS) { - goto fail; - } - gnutls_anti_replay_set_add_function(creds->tls_anti_replay, tls_anti_replay_db_add_func); - gnutls_anti_replay_set_ptr(creds->tls_anti_replay, NULL); - - if (cert_file != NULL) { - ret = gnutls_certificate_set_x509_key_file(creds->tls_cert, - cert_file, key_file, - GNUTLS_X509_FMT_PEM); - } else { - ret = self_signed_cert(creds->tls_cert, key_file); - } - if (ret != GNUTLS_E_SUCCESS) { - goto fail; - } - - ret = gnutls_session_ticket_key_generate(&creds->tls_ticket_key); - if (ret != GNUTLS_E_SUCCESS) { - goto fail; - } - - return creds; -fail: - knot_quic_free_creds(creds); - return NULL; -} - -_public_ -struct knot_quic_creds *knot_quic_init_creds_peer(const struct knot_quic_creds *local_creds, - const uint8_t *peer_pin, - uint8_t peer_pin_len) -{ - knot_quic_creds_t *creds = calloc(1, sizeof(*creds) + peer_pin_len); - if (creds == NULL) { - return NULL; - } - - if (local_creds != NULL) { - creds->peer = true; - creds->tls_cert = local_creds->tls_cert; - } else { - int ret = gnutls_certificate_allocate_credentials(&creds->tls_cert); - if (ret != GNUTLS_E_SUCCESS) { - free(creds); - return NULL; - } - } - - if (peer_pin_len > 0 && peer_pin != NULL) { - memcpy(creds->peer_pin, peer_pin, peer_pin_len); - creds->peer_pin_len = peer_pin_len; - } - - return creds; -} - -_public_ -int knot_quic_creds_cert(struct knot_quic_creds *creds, struct gnutls_x509_crt_int **cert) -{ - if (creds == NULL || cert == NULL) { - return KNOT_EINVAL; - } - - gnutls_x509_crt_t *certs; - unsigned cert_count; - int ret = gnutls_certificate_get_x509_crt(creds->tls_cert, 0, &certs, &cert_count); - if (ret == GNUTLS_E_SUCCESS) { - if (cert_count == 0) { - gnutls_x509_crt_deinit(*certs); - return KNOT_ENOENT; - } - *cert = *certs; - free(certs); - } - return ret; -} - -_public_ -void knot_quic_free_creds(struct knot_quic_creds *creds) -{ - if (creds == NULL) { - return; - } - - if (!creds->peer && creds->tls_cert != NULL) { - gnutls_certificate_free_credentials(creds->tls_cert); - } - gnutls_anti_replay_deinit(creds->tls_anti_replay); - if (creds->tls_ticket_key.data != NULL) { - tls_session_ticket_key_free(&creds->tls_ticket_key); - } - free(creds); -} - static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { return ((knot_quic_conn_t *)conn_ref->user_data)->conn; @@ -377,51 +146,31 @@ static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) static int tls_init_conn_session(knot_quic_conn_t *conn, bool server) { - if (gnutls_init(&conn->tls_session, (server ? GNUTLS_SERVER : GNUTLS_CLIENT) | - GNUTLS_ENABLE_EARLY_DATA | GNUTLS_NO_AUTO_SEND_TICKET | - GNUTLS_NO_END_OF_EARLY_DATA) != GNUTLS_E_SUCCESS) { - return TLS_CALLBACK_ERR; - } - - gnutls_certificate_send_x509_rdn_sequence(conn->tls_session, 1); - gnutls_certificate_server_set_request(conn->tls_session, GNUTLS_CERT_REQUEST); - - if (gnutls_priority_set_direct(conn->tls_session, QUIC_PRIORITIES, - NULL) != GNUTLS_E_SUCCESS) { + int ret = knot_tls_session(&conn->tls_session, conn->quic_table->creds, + conn->quic_table->priority, "\x03""doq", + true, server); + if (ret != KNOT_EOK) { return TLS_CALLBACK_ERR; } - if (server && gnutls_session_ticket_enable_server(conn->tls_session, - &conn->quic_table->creds->tls_ticket_key) != GNUTLS_E_SUCCESS) { - return TLS_CALLBACK_ERR; + if (server) { + ret = ngtcp2_crypto_gnutls_configure_server_session(conn->tls_session); + } else { + ret = ngtcp2_crypto_gnutls_configure_client_session(conn->tls_session); } - - int ret = ngtcp2_crypto_gnutls_configure_server_session(conn->tls_session); - if (ret != 0) { + if (ret != NGTCP2_NO_ERROR) { return TLS_CALLBACK_ERR; } - gnutls_record_set_max_early_data_size(conn->tls_session, 0xffffffffu); - conn->conn_ref = (nc_conn_ref_placeholder_t) { .get_conn = get_conn, .user_data = conn }; - _Static_assert(sizeof(nc_conn_ref_placeholder_t) == sizeof(ngtcp2_crypto_conn_ref), "invalid placeholder for conn_ref"); + _Static_assert(sizeof(nc_conn_ref_placeholder_t) == sizeof(ngtcp2_crypto_conn_ref), + "invalid placeholder for conn_ref"); gnutls_session_set_ptr(conn->tls_session, &conn->conn_ref); - if (server) { - gnutls_anti_replay_enable(conn->tls_session, conn->quic_table->creds->tls_anti_replay); - - } - if (gnutls_credentials_set(conn->tls_session, GNUTLS_CRD_CERTIFICATE, - conn->quic_table->creds->tls_cert) != GNUTLS_E_SUCCESS) { - return TLS_CALLBACK_ERR; - } - - gnutls_alpn_set_protocols(conn->tls_session, &doq_alpn, 1, GNUTLS_ALPN_MANDATORY); - ngtcp2_conn_set_tls_native_handle(conn->conn, conn->tls_session); return KNOT_EOK; @@ -477,54 +226,6 @@ uint16_t knot_quic_conn_local_port(knot_quic_conn_t *conn) return ((const struct sockaddr_in6 *)path->local.addr)->sin6_port; } -_public_ -void knot_quic_conn_pin(knot_quic_conn_t *conn, uint8_t *pin, size_t *pin_size, bool local) -{ - if (conn == NULL) { - goto error; - } - - const gnutls_datum_t *data = NULL; - if (local) { - data = gnutls_certificate_get_ours(conn->tls_session); - } else { - unsigned count = 0; - data = gnutls_certificate_get_peers(conn->tls_session, &count); - if (count == 0) { - goto error; - } - } - if (data == NULL) { - goto error; - } - - gnutls_x509_crt_t cert; - int ret = gnutls_x509_crt_init(&cert); - if (ret != GNUTLS_E_SUCCESS) { - goto error; - } - - ret = gnutls_x509_crt_import(cert, data, GNUTLS_X509_FMT_DER); - if (ret != GNUTLS_E_SUCCESS) { - gnutls_x509_crt_deinit(cert); - goto error; - } - - ret = gnutls_x509_crt_get_key_id(cert, GNUTLS_KEYID_USE_SHA256, pin, pin_size); - if (ret != GNUTLS_E_SUCCESS) { - gnutls_x509_crt_deinit(cert); - goto error; - } - - gnutls_x509_crt_deinit(cert); - - return; -error: - if (pin_size != NULL) { - *pin_size = 0; - } -} - static void knot_quic_rand_cb(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { (void)rand_ctx; @@ -602,18 +303,8 @@ static int handshake_completed_cb(ngtcp2_conn *conn, void *user_data) ctx->flags |= KNOT_QUIC_CONN_HANDSHAKE_DONE; if (!ngtcp2_conn_is_server(conn)) { - knot_quic_creds_t *creds = ctx->quic_table->creds; - if (creds->peer_pin_len == 0) { - return 0; - } - uint8_t pin[KNOT_QUIC_PIN_LEN]; - size_t pin_size = sizeof(pin); - knot_quic_conn_pin(ctx, pin, &pin_size, false); - if (pin_size != creds->peer_pin_len || - const_time_memcmp(pin, creds->peer_pin, pin_size) != 0) { - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; + return knot_tls_pin_check(ctx->tls_session, ctx->quic_table->creds) + == KNOT_EOK ? 0 : NGTCP2_ERR_CALLBACK_FAILURE; } if (gnutls_session_ticket_send(ctx->tls_session, 1, 0) != GNUTLS_E_SUCCESS) { @@ -945,6 +636,10 @@ int knot_quic_handle(knot_quic_table_t *table, knot_quic_reply_t *reply, goto finish; } + if (conn != NULL && (conn->flags & KNOT_QUIC_CONN_BLOCKED)) { + return KNOT_EOK; + } + ngtcp2_path path; path.remote.addr = (struct sockaddr *)reply->ip_rem; path.remote.addrlen = addr_len((struct sockaddr_in6 *)reply->ip_rem); @@ -1249,6 +944,8 @@ int knot_quic_send(knot_quic_table_t *quic_table, knot_quic_conn_t *conn, return KNOT_EINVAL; } else if (reply->handle_ret < 0) { return reply->handle_ret; + } else if ((conn->flags & KNOT_QUIC_CONN_BLOCKED) && !(flags & KNOT_QUIC_SEND_IGNORE_BLOCKED)) { + return KNOT_EOK; } else if (reply->handle_ret > 0) { return send_special(quic_table, reply, conn); } else if (conn == NULL) { |