]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
libknot/quic: code moved
authorLibor Peltan <libor.peltan@nic.cz>
Fri, 23 Feb 2024 14:12:33 +0000 (15:12 +0100)
committerDaniel Salzman <daniel.salzman@nic.cz>
Tue, 7 May 2024 05:52:29 +0000 (07:52 +0200)
Knot.files
src/libknot/Makefile.inc
src/libknot/quic/quic.c
src/libknot/quic/quic.h
src/libknot/quic/tls_common.c [new file with mode: 0644]
src/libknot/quic/tls_common.h [new file with mode: 0644]

index 279ca4c5738ecad099d8e16949bac6b1da3c6b3a..4286999381302974f75fcfeb0798f0da42a27cda 100644 (file)
@@ -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
index f62d836268424c81d908f0f05c82ac5f684bed5b..dc39b5fb729169d53d26700e47981e50059602d6 100755 (executable)
@@ -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                         \
index 9f372007bc69954080e46e76a3c59e3c6293bbf9..67b875a5d16c86afb66822c909a196a45fcf1bf0 100644 (file)
@@ -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;
index 6d8841577c6623c1769d7448a69f5dc4e9e182e9..1af614208e7387013d646044c3748402bf991fd3 100644 (file)
 #include <netinet/in.h>
 
 #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 (file)
index 0000000..07ca809
--- /dev/null
@@ -0,0 +1,258 @@
+/*  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
+    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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "libknot/quic/tls_common.h"
+
+#include "contrib/sockaddr.h"
+#include "contrib/string.h"
+#include "libknot/attribute.h"
+#include "libknot/error.h"
+
+#include <fcntl.h>
+#include <gnutls/crypto.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+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 (file)
index 0000000..cf4b3bd
--- /dev/null
@@ -0,0 +1,66 @@
+/*  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
+    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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#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);