From: Libor Peltan Date: Fri, 23 Feb 2024 14:12:33 +0000 (+0100) Subject: libknot/quic: code moved X-Git-Tag: v3.4.0~110^2~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7910802cef144b920d4c2a3e34fec81bfec199d8;p=thirdparty%2Fknot-dns.git libknot/quic: code moved --- diff --git a/Knot.files b/Knot.files index 279ca4c573..4286999381 100644 --- a/Knot.files +++ b/Knot.files @@ -490,6 +490,8 @@ src/libknot/quic/quic.c src/libknot/quic/quic.h src/libknot/quic/quic_conn.c src/libknot/quic/quic_conn.h +src/libknot/quic/tls_common.c +src/libknot/quic/tls_common.h src/libknot/rdata.h src/libknot/rdataset.c src/libknot/rdataset.h diff --git a/src/libknot/Makefile.inc b/src/libknot/Makefile.inc index f62d836268..dc39b5fb72 100755 --- a/src/libknot/Makefile.inc +++ b/src/libknot/Makefile.inc @@ -37,6 +37,7 @@ nobase_include_libknot_HEADERS = \ libknot/packet/wire.h \ libknot/probe/data.h \ libknot/probe/probe.h \ + libknot/quic/tls_common.h \ libknot/rdata.h \ libknot/rdataset.h \ libknot/rrset-dump.h \ @@ -78,6 +79,7 @@ libknot_la_SOURCES = \ libknot/packet/rrset-wire.c \ libknot/probe/data.c \ libknot/probe/probe.c \ + libknot/quic/tls_common.c \ libknot/rdataset.c \ libknot/rrset-dump.c \ libknot/rrset.c \ diff --git a/src/libknot/quic/quic.c b/src/libknot/quic/quic.c index 9f372007bc..67b875a5d1 100644 --- a/src/libknot/quic/quic.c +++ b/src/libknot/quic/quic.c @@ -153,223 +153,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) -{ - memzero(ticket->data, 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; diff --git a/src/libknot/quic/quic.h b/src/libknot/quic/quic.h index 6d8841577c..1af614208e 100644 --- a/src/libknot/quic/quic.h +++ b/src/libknot/quic/quic.h @@ -29,16 +29,13 @@ #include #include "libknot/quic/quic_conn.h" - -#define KNOT_QUIC_PIN_LEN 32 +#include "libknot/quic/tls_common.h" #define KNOT_QUIC_HANDLE_RET_CLOSE 2000 // RFC 9250 #define KNOT_QUIC_ERR_EXCESSIVE_LOAD 0x4 -struct gnutls_x509_crt_int; -struct knot_quic_creds; struct knot_quic_session; typedef enum { @@ -87,45 +84,6 @@ struct knot_quic_session *knot_quic_session_save(knot_quic_conn_t *conn); */ int knot_quic_session_load(knot_quic_conn_t *conn, struct knot_quic_session *session); -/*! - * \brief Init server TLS certificate for DoQ. - * - * \param cert_file X509 certificate PEM file path/name (NULL if auto-generated). - * \param key_file Key PEM file path/name. - * - * \return Initialized creds. - */ -struct knot_quic_creds *knot_quic_init_creds(const char *cert_file, - const char *key_file); - -/*! - * \brief Init peer TLS certificate for DoQ. - * - * \param local_creds Local credentials if server. - * \param peer_pin Optional peer certificate pin to check. - * \param peer_pin_len Length of the peer pin. Set 0 if not specified. - * - * \return Initialized creds. - */ -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); - -/*! - * \brief Gets the certificate from credentials. - * - * \param creds TLS credentials. - * \param cert Output certificate. - * - * \return KNOT_E* - */ -int knot_quic_creds_cert(struct knot_quic_creds *creds, struct gnutls_x509_crt_int **cert); - -/*! - * \brief Deinit server TLS certificate for DoQ. - */ -void knot_quic_free_creds(struct knot_quic_creds *creds); - /*! * \brief Returns timeout value for the connection. */ diff --git a/src/libknot/quic/tls_common.c b/src/libknot/quic/tls_common.c new file mode 100644 index 0000000000..07ca8093e5 --- /dev/null +++ b/src/libknot/quic/tls_common.c @@ -0,0 +1,258 @@ +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. + + 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "libknot/quic/tls_common.h" + +#include "contrib/sockaddr.h" +#include "contrib/string.h" +#include "libknot/attribute.h" +#include "libknot/error.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; + +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) +{ + memzero(ticket->data, 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); +} diff --git a/src/libknot/quic/tls_common.h b/src/libknot/quic/tls_common.h new file mode 100644 index 0000000000..cf4b3bdf4d --- /dev/null +++ b/src/libknot/quic/tls_common.h @@ -0,0 +1,66 @@ +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. + + 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include + +#define KNOT_QUIC_PIN_LEN 32 + +struct gnutls_session_int; +struct gnutls_x509_crt_int; +struct knot_quic_creds; + +/*! + * \brief Init server TLS certificate for DoQ. + * + * \param cert_file X509 certificate PEM file path/name (NULL if auto-generated). + * \param key_file Key PEM file path/name. + * + * \return Initialized creds. + */ +struct knot_quic_creds *knot_quic_init_creds(const char *cert_file, + const char *key_file); + +/*! + * \brief Init peer TLS certificate for DoQ. + * + * \param local_creds Local credentials if server. + * \param peer_pin Optional peer certificate pin to check. + * \param peer_pin_len Length of the peer pin. Set 0 if not specified. + * + * \return Initialized creds. + */ +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); + +/*! + * \brief Gets the certificate from credentials. + * + * \param creds TLS credentials. + * \param cert Output certificate. + * + * \return KNOT_E* + */ +int knot_quic_creds_cert(struct knot_quic_creds *creds, struct gnutls_x509_crt_int **cert); + +/*! + * \brief Deinit server TLS certificate for DoQ. + */ +void knot_quic_free_creds(struct knot_quic_creds *creds);