config UPDATE implemented CRL for TLS

Certificate Revocation List now supported, this means a new dependency -
libcurl.
diff --git a/.github/workflows/libnetconf3-ci.yml b/.github/workflows/libnetconf3-ci.yml
index 8b6d2c5..1d41a04 100644
--- a/.github/workflows/libnetconf3-ci.yml
+++ b/.github/workflows/libnetconf3-ci.yml
@@ -8,7 +8,7 @@
       - libnetconf3
 
 env:
-  DEFAULT_PACKAGES: libcmocka-dev zlib1g-dev libssh-dev libssl-dev libpam0g-dev
+  DEFAULT_PACKAGES: libcmocka-dev zlib1g-dev libssh-dev libssl-dev libpam0g-dev libcurl4-openssl-dev
 
 jobs:
   build:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 98b4ad8..ac26d47 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -239,6 +239,10 @@
     list(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBSSH_LIBRARIES})
     include_directories(${LIBSSH_INCLUDE_DIRS})
 
+    # dependencies - libcurl
+    find_package(CURL 7.30.0 REQUIRED)
+    target_link_libraries(netconf2 CURL::libcurl)
+
     # crypt
     if(${CMAKE_SYSTEM_NAME} MATCHES "QNX")
         target_link_libraries(netconf2 -llogin)
diff --git a/modules/libnetconf2-netconf-server.yang b/modules/libnetconf2-netconf-server.yang
index 185dafc..ec0d6f0 100644
--- a/modules/libnetconf2-netconf-server.yang
+++ b/modules/libnetconf2-netconf-server.yang
@@ -27,6 +27,10 @@
     prefix sshma;
   }
 
+  import ietf-tls-server {
+    prefix tlss;
+  }
+
   /*
   identity ed25519-private-key-format {
     base ct:private-key-format;
@@ -309,4 +313,46 @@
       must "deref(.)/../*[local-name() = 'tls']";
     }
   }
+
+  augment "/ncs:netconf-server/ncs:listen/ncs:endpoint/ncs:transport/ncs:tls/ncs:tls/ncs:tls-server-parameters/ncs:client-authentication" {
+    description
+      "Indicates that the TLS server is using a Certificate Revocation List
+       to authenticate clients or to deny access for certain certificates.
+       The given Certificate Revocation List must be PEM or DER encoded.";
+
+    reference
+      "RFC 5280:
+         Internet X.509 Public Key Infrastructure Certificate
+         and Certificate Revocation List (CRL) Profile";
+
+    choice certificate-revocation-list {
+      leaf crl-url {
+        type string;
+        description
+          "An URL from which the Certificate Revocation List will be
+           downloaded and used. The HTTP protocol works, but other
+           protocols, such as FTP, may work as well.";
+      }
+
+      leaf crl-path {
+        type string;
+        description
+          "A path to a Certificate Revocation List file.";
+      }
+
+      leaf crl-cert-ext {
+        type empty;
+        description
+          "Indicates that the Certificate Revocation List
+           Distribution Points extension will be used to fetch
+           Certificate Revocation Lists from. This will be done
+           for all the configured Certificate Authority certificates.";
+
+        reference
+          "RFC 5280:
+             Internet X.509 Public Key Infrastructure Certificate
+             and Certificate Revocation List (CRL) Profile, Section 4.2.1.13";
+      }
+    }
+  }
 }
diff --git a/src/config_new_tls.c b/src/config_new_tls.c
index eb05ba9..2a2036c 100644
--- a/src/config_new_tls.c
+++ b/src/config_new_tls.c
@@ -570,3 +570,231 @@
     free(tree_path);
     return ret;
 }
+
+API int
+nc_server_config_new_tls_crl_path(const struct ly_ctx *ctx, const char *endpt_name, const char *path, struct lyd_node **config)
+{
+    int ret = 0;
+    struct lyd_node *new_tree, *node = NULL;
+    char *tree_path = NULL;
+    struct lys_module *mod;
+
+    NC_CHECK_ARG_RET(NULL, ctx, endpt_name, path, config, 1);
+
+    /* prepare path for instertion of leaves later */
+    asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/tls/tls-server-parameters/"
+            "client-authentication", endpt_name);
+    if (!tree_path) {
+        ERRMEM;
+        ret = 1;
+        goto cleanup;
+    }
+
+    /* create all the nodes in the path */
+    ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree);
+    if (ret) {
+        goto cleanup;
+    }
+    if (!*config) {
+        *config = new_tree;
+    }
+
+    if (!new_tree) {
+        /* no new nodes were created */
+        ret = lyd_find_path(*config, tree_path, 0, &new_tree);
+    } else {
+        /* config was NULL */
+        ret = lyd_find_path(new_tree, tree_path, 0, &new_tree);
+    }
+    if (ret) {
+        goto cleanup;
+    }
+
+    /* delete other choice nodes if they are present */
+    lyd_find_path(new_tree, "libnetconf2-netconf-server:crl-url", 0, &node);
+    lyd_free_tree(node);
+    lyd_find_path(new_tree, "libnetconf2-netconf-server:crl-cert-ext", 0, &node);
+    lyd_free_tree(node);
+
+    /* get the wanted module, because parent of the inserted node has a different one */
+    mod = ly_ctx_get_module_implemented(ctx, "libnetconf2-netconf-server");
+    if (!mod) {
+        ERR(NULL, "Error getting libnetconf2-netconf-server module.");
+        ret = 1;
+        goto cleanup;
+    }
+
+    ret = lyd_new_term(new_tree, mod, "crl-path", path, 0, NULL);
+    if (ret) {
+        ERR(NULL, "Creating new Certificate Revocation List node failed.");
+        goto cleanup;
+    }
+
+    /* check if top-level container has operation and if not, add it */
+    ret = nc_config_new_check_add_operation(ctx, *config);
+    if (ret) {
+        goto cleanup;
+    }
+
+    /* Add all default nodes */
+    ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
+    if (ret) {
+        goto cleanup;
+    }
+
+cleanup:
+    free(tree_path);
+    return ret;
+}
+
+API int
+nc_server_config_new_tls_crl_url(const struct ly_ctx *ctx, const char *endpt_name, const char *url, struct lyd_node **config)
+{
+    int ret = 0;
+    struct lyd_node *new_tree, *node = NULL;
+    char *tree_path = NULL;
+    struct lys_module *mod;
+
+    NC_CHECK_ARG_RET(NULL, ctx, endpt_name, url, config, 1);
+
+    /* prepare path for instertion of leaves later */
+    asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/tls/tls-server-parameters/"
+            "client-authentication", endpt_name);
+    if (!tree_path) {
+        ERRMEM;
+        ret = 1;
+        goto cleanup;
+    }
+
+    /* create all the nodes in the path */
+    ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree);
+    if (ret) {
+        goto cleanup;
+    }
+    if (!*config) {
+        *config = new_tree;
+    }
+
+    if (!new_tree) {
+        /* no new nodes were created */
+        ret = lyd_find_path(*config, tree_path, 0, &new_tree);
+    } else {
+        /* config was NULL */
+        ret = lyd_find_path(new_tree, tree_path, 0, &new_tree);
+    }
+    if (ret) {
+        goto cleanup;
+    }
+
+    /* delete other choice nodes if they are present */
+    lyd_find_path(new_tree, "libnetconf2-netconf-server:crl-path", 0, &node);
+    lyd_free_tree(node);
+    lyd_find_path(new_tree, "libnetconf2-netconf-server:crl-cert-ext", 0, &node);
+    lyd_free_tree(node);
+
+    /* get the wanted module, because parent of the inserted node has a different one */
+    mod = ly_ctx_get_module_implemented(ctx, "libnetconf2-netconf-server");
+    if (!mod) {
+        ERR(NULL, "Error getting libnetconf2-netconf-server module.");
+        ret = 1;
+        goto cleanup;
+    }
+
+    ret = lyd_new_term(new_tree, mod, "crl-url", url, 0, NULL);
+    if (ret) {
+        ERR(NULL, "Creating new Certificate Revocation List node failed.");
+        goto cleanup;
+    }
+
+    /* check if top-level container has operation and if not, add it */
+    ret = nc_config_new_check_add_operation(ctx, *config);
+    if (ret) {
+        goto cleanup;
+    }
+
+    /* Add all default nodes */
+    ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
+    if (ret) {
+        goto cleanup;
+    }
+
+cleanup:
+    free(tree_path);
+    return ret;
+}
+
+API int
+nc_server_config_new_tls_crl_cert_ext(const struct ly_ctx *ctx, const char *endpt_name, struct lyd_node **config)
+{
+    int ret = 0;
+    struct lyd_node *new_tree, *node = NULL;
+    char *tree_path = NULL;
+    struct lys_module *mod;
+
+    NC_CHECK_ARG_RET(NULL, ctx, endpt_name, config, 1);
+
+    /* prepare path for instertion of leaves later */
+    asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/tls/tls-server-parameters/"
+            "client-authentication", endpt_name);
+    if (!tree_path) {
+        ERRMEM;
+        ret = 1;
+        goto cleanup;
+    }
+
+    /* create all the nodes in the path */
+    ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree);
+    if (ret) {
+        goto cleanup;
+    }
+    if (!*config) {
+        *config = new_tree;
+    }
+
+    if (!new_tree) {
+        /* no new nodes were created */
+        ret = lyd_find_path(*config, tree_path, 0, &new_tree);
+    } else {
+        /* config was NULL */
+        ret = lyd_find_path(new_tree, tree_path, 0, &new_tree);
+    }
+    if (ret) {
+        goto cleanup;
+    }
+
+    /* delete other choice nodes if they are present */
+    lyd_find_path(new_tree, "libnetconf2-netconf-server:crl-path", 0, &node);
+    lyd_free_tree(node);
+    lyd_find_path(new_tree, "libnetconf2-netconf-server:crl-url", 0, &node);
+    lyd_free_tree(node);
+
+    /* get the wanted module, because parent of the inserted node has a different one */
+    mod = ly_ctx_get_module_implemented(ctx, "libnetconf2-netconf-server");
+    if (!mod) {
+        ERR(NULL, "Error getting libnetconf2-netconf-server module.");
+        ret = 1;
+        goto cleanup;
+    }
+
+    ret = lyd_new_term(new_tree, mod, "crl-cert-ext", NULL, 0, NULL);
+    if (ret) {
+        ERR(NULL, "Creating new Certificate Revocation List node failed.");
+        goto cleanup;
+    }
+
+    /* check if top-level container has operation and if not, add it */
+    ret = nc_config_new_check_add_operation(ctx, *config);
+    if (ret) {
+        goto cleanup;
+    }
+
+    /* Add all default nodes */
+    ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
+    if (ret) {
+        goto cleanup;
+    }
+
+cleanup:
+    free(tree_path);
+    return ret;
+}
diff --git a/src/server_config.c b/src/server_config.c
index ff0265f..a94bc09 100644
--- a/src/server_config.c
+++ b/src/server_config.c
@@ -25,6 +25,10 @@
 
 #include <libyang/libyang.h>
 
+#ifdef NC_ENABLED_SSH_TLS
+#include <openssl/x509_vfy.h> // X509_STORE_free
+#endif
+
 #include "compat.h"
 #include "config.h"
 #include "log_p.h"
@@ -670,6 +674,20 @@
 #ifdef NC_ENABLED_SSH_TLS
 
 static void
+nc_server_config_del_url(struct nc_server_tls_opts *opts)
+{
+    free(opts->crl_url);
+    opts->crl_url = NULL;
+}
+
+static void
+nc_server_config_del_path(struct nc_server_tls_opts *opts)
+{
+    free(opts->crl_path);
+    opts->crl_path = NULL;
+}
+
+static void
 nc_server_config_tls_del_ciphers(struct nc_server_tls_opts *opts)
 {
     free(opts->ciphers);
@@ -804,6 +822,11 @@
     nc_server_config_tls_del_certs(&opts->ca_certs);
     nc_server_config_tls_del_certs(&opts->ee_certs);
 
+    nc_server_config_del_path(opts);
+    nc_server_config_del_url(opts);
+    X509_STORE_free(opts->crl_store);
+    opts->crl_store = NULL;
+
     nc_server_config_tls_del_ctns(opts);
     nc_server_config_tls_del_ciphers(opts);
 
@@ -3145,6 +3168,8 @@
     struct nc_endpt *endpt;
     const char *cipher = NULL;
 
+    assert(!strcmp(LYD_NAME(node), "cipher-suite"));
+
     if (nc_server_config_get_endpt(node, &endpt, NULL)) {
         ret = 1;
         goto cleanup;
@@ -3167,6 +3192,87 @@
     return ret;
 }
 
+static int
+nc_server_config_crl_url(const struct lyd_node *node, NC_OPERATION op)
+{
+    int ret = 0;
+    struct nc_endpt *endpt;
+
+    assert(!strcmp(LYD_NAME(node), "crl-url"));
+
+    if (nc_server_config_get_endpt(node, &endpt, NULL)) {
+        ret = 1;
+        goto cleanup;
+    }
+
+    if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
+        nc_server_config_del_url(endpt->opts.tls);
+        endpt->opts.tls->crl_url = strdup(lyd_get_value(node));
+        if (!endpt->opts.tls->crl_url) {
+            ERRMEM;
+            ret = 1;
+            goto cleanup;
+        }
+    } else if (op == NC_OP_DELETE) {
+        nc_server_config_del_url(endpt->opts.tls);
+    }
+
+cleanup:
+    return ret;
+}
+
+static int
+nc_server_config_crl_path(const struct lyd_node *node, NC_OPERATION op)
+{
+    int ret = 0;
+    struct nc_endpt *endpt;
+
+    assert(!strcmp(LYD_NAME(node), "crl-path"));
+
+    if (nc_server_config_get_endpt(node, &endpt, NULL)) {
+        ret = 1;
+        goto cleanup;
+    }
+
+    if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
+        nc_server_config_del_path(endpt->opts.tls);
+        endpt->opts.tls->crl_path = strdup(lyd_get_value(node));
+        if (!endpt->opts.tls->crl_path) {
+            ERRMEM;
+            ret = 1;
+            goto cleanup;
+        }
+    } else if (op == NC_OP_DELETE) {
+        nc_server_config_del_path(endpt->opts.tls);
+    }
+
+cleanup:
+    return ret;
+}
+
+static int
+nc_server_config_crl_cert_ext(const struct lyd_node *node, NC_OPERATION op)
+{
+    int ret = 0;
+    struct nc_endpt *endpt;
+
+    assert(!strcmp(LYD_NAME(node), "crl-cert-ext"));
+
+    if (nc_server_config_get_endpt(node, &endpt, NULL)) {
+        ret = 1;
+        goto cleanup;
+    }
+
+    if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) {
+        endpt->opts.tls->crl_cert_ext = 1;
+    } else if (op == NC_OP_DELETE) {
+        endpt->opts.tls->crl_cert_ext = 0;
+    }
+
+cleanup:
+    return ret;
+}
+
 #endif /* NC_ENABLED_SSH_TLS */
 
 static int
@@ -3328,6 +3434,18 @@
         if (nc_server_config_cipher_suite(node, op)) {
             goto error;
         }
+    } else if (!strcmp(name, "crl-url")) {
+        if (nc_server_config_crl_url(node, op)) {
+            goto error;
+        }
+    } else if (!strcmp(name, "crl-path")) {
+        if (nc_server_config_crl_path(node, op)) {
+            goto error;
+        }
+    } else if (!strcmp(name, "crl-cert-ext")) {
+        if (nc_server_config_crl_cert_ext(node, op)) {
+            goto error;
+        }
     }
 #endif /* NC_ENABLED_SSH_TLS */
 
diff --git a/src/server_config.h b/src/server_config.h
index d75de3b..02362f2 100644
--- a/src/server_config.h
+++ b/src/server_config.h
@@ -363,6 +363,56 @@
 int nc_server_config_new_tls_ciphers(const struct ly_ctx *ctx, const char *endpt_name, struct lyd_node **config,
         int cipher_count, ...);
 
+/**
+ * @brief Creates new YANG configuration data nodes for a Certificate Revocation List via a local file.
+ *
+ * Beware that you can choose up to one function between the three CRL alternatives on a given endpoint and calling
+ * this function will remove any CRL YANG nodes created by the other two functions.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] endpt_name Arbitrary identifier of the endpoint.
+ * If an endpoint with this identifier already exists, it's contents will be changed.
+ * @param[in] path Path to a DER/PEM encoded CRL file.
+ * @param[in,out] config Configuration YANG data tree. If *config is NULL, it will be created.
+ * Otherwise the new YANG data will be added to the previous data and may override it.
+ * @return 0 on success, non-zero otherwise.
+ */
+int nc_server_config_new_tls_crl_path(const struct ly_ctx *ctx, const char *endpt_name, const char *path, struct lyd_node **config);
+
+/**
+ * @brief Creates new YANG configuration data nodes for a Certificate Revocation List via an URL.
+ *
+ * Beware that you can choose up to one function between the three CRL alternatives on a given endpoint and calling
+ * this function will remove any CRL YANG nodes created by the other two functions.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] endpt_name Arbitrary identifier of the endpoint.
+ * If an endpoint with this identifier already exists, it's contents will be changed.
+ * @param[in] url URL from which the CRL file will be downloaded. The file has to be in the DER or PEM format.
+ * The allowed protocols are all the protocols supported by CURL.
+ * @param[in,out] config Configuration YANG data tree. If *config is NULL, it will be created.
+ * Otherwise the new YANG data will be added to the previous data and may override it.
+ * @return 0 on success, non-zero otherwise.
+ */
+int nc_server_config_new_tls_crl_url(const struct ly_ctx *ctx, const char *endpt_name, const char *url, struct lyd_node **config);
+
+/**
+ * @brief Creates new YANG configuration data nodes for a Certificate Revocation List via certificate extensions.
+ *
+ * The chain of configured Certificate Authorities will be examined. For each certificate in this chain all the
+ * CRLs from the URLs specified in their extension fields CRL Distribution Points will be downloaded and used.
+ * Beware that you can choose up to one function between the three CRL alternatives on a given endpoint and calling
+ * this function will remove any CRL YANG nodes created by the other two functions.
+ *
+ * @param[in] ctx libyang context.
+ * @param[in] endpt_name Arbitrary identifier of the endpoint.
+ * If an endpoint with this identifier already exists, it's contents will be changed.
+ * @param[in,out] config Configuration YANG data tree. If *config is NULL, it will be created.
+ * Otherwise the new YANG data will be added to the previous data and may override it.
+ * @return 0 on success, non-zero otherwise.
+ */
+int nc_server_config_new_tls_crl_cert_ext(const struct ly_ctx *ctx, const char *endpt_name, struct lyd_node **config);
+
 #endif /* NC_ENABLED_SSH_TLS */
 
 #ifdef __cplusplus
diff --git a/src/session_p.h b/src/session_p.h
index 16100c4..13dfd31 100644
--- a/src/session_p.h
+++ b/src/session_p.h
@@ -227,6 +227,14 @@
 };
 
 /**
+ * @brief Storing downloaded data via CURL.
+ */
+struct nc_curl_data {
+    unsigned char *data;    /**< Downloaded data */
+    size_t size;            /**< Size of downloaded data */
+};
+
+/**
  * @brief Cert-to-name entries.
  */
 struct nc_ctn {
@@ -261,6 +269,10 @@
 
     struct nc_cert_grouping ca_certs;           /**< Client certificate authorities */
     struct nc_cert_grouping ee_certs;           /**< Client end-entity certificates */
+    char *crl_url;                              /**< URI to download the CRL from */
+    char *crl_path;                             /**< Path to a CRL file */
+    int crl_cert_ext;                           /**< Indicates to use CA's distribution points to obtain CRLs */
+    X509_STORE *crl_store;                      /**< Stores all the CRLs */
 
     unsigned int tls_versions;                  /**< TLS versions */
     char *ciphers;                              /**< TLS ciphers */
diff --git a/src/session_server_tls.c b/src/session_server_tls.c
index 5f5c6ea..ea63bc8 100644
--- a/src/session_server_tls.c
+++ b/src/session_server_tls.c
@@ -599,6 +599,7 @@
 }
 
 static int
+<<<<<<< HEAD
 nc_server_tls_ts_ref_get_certs(const char *referenced_name, struct nc_certificate **certs, uint16_t *cert_count)
 {
     uint16_t i;
@@ -681,17 +682,29 @@
 }
 
 static int
+=======
+>>>>>>> d301c76 (config UPDATE implemented CRL for TLS)
 nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx)
 {
     X509_NAME *subject;
     X509_NAME *issuer;
     X509 *cert;
+<<<<<<< HEAD
+=======
+    struct nc_cert_grouping *ee_certs;
+>>>>>>> d301c76 (config UPDATE implemented CRL for TLS)
     char *cp;
 
     STACK_OF(X509) * cert_stack;
     struct nc_session *session;
     struct nc_server_tls_opts *opts;
+<<<<<<< HEAD
     int rc, depth;
+=======
+    int i, rc, depth;
+    const char *username = NULL;
+    NC_TLS_CTN_MAPTYPE map_type = 0;
+>>>>>>> d301c76 (config UPDATE implemented CRL for TLS)
 
     /* get the thread session */
     session = pthread_getspecific(verify_key);
@@ -712,14 +725,23 @@
 
     /* standard certificate verification failed, so an end-entity client cert must match to continue */
     if (!preverify_ok) {
+<<<<<<< HEAD
         /* check current endpoint's end-entity certs */
         rc = nc_server_tls_do_preverify(session, x509_ctx, 1);
         if (rc == -1) {
+=======
+        /* get the store from the current context */
+        X509_STORE *store = X509_STORE_CTX_get0_store(x509_ctx);
+
+        if (!store) {
+            ERR(session, "Error getting store from context (%s).", ERR_reason_error_string(ERR_get_error()));
+>>>>>>> d301c76 (config UPDATE implemented CRL for TLS)
             return 0;
         } else if (rc == 1) {
             return 1;
         }
 
+<<<<<<< HEAD
         /* no match, continue */
         if (opts->endpt_client_ref) {
             /* check referenced endpoint's end-entity certs */
@@ -727,11 +749,33 @@
             if (rc == -1) {
                 return 0;
             } else if (rc == 1) {
+=======
+        /* get the data from the store */
+        ee_certs = X509_STORE_get_ex_data(store, 1);
+        if (!ee_certs) {
+            ERR(session, "Error getting data from store (%s).", ERR_reason_error_string(ERR_get_error()));
+            return 0;
+        }
+
+        for (i = 0; i < ee_certs->cert_count; i++) {
+            cert = base64der_to_cert(ee_certs->certs[i].data);
+            rc = cert_pubkey_match(session->opts.server.client_cert, cert);
+            X509_free(cert);
+            if (rc) {
+                /* we are just overriding the failed standard certificate verification (preverify_ok == 0),
+                 * this callback will be called again with the same current certificate and preverify_ok == 1 */
+                VRB(session, "Cert verify: fail (%s), but the end-entity certificate is trusted, continuing.",
+                        X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509_ctx)));
+                X509_STORE_CTX_set_error(x509_ctx, X509_V_OK);
+>>>>>>> d301c76 (config UPDATE implemented CRL for TLS)
                 return 1;
             }
         }
 
+<<<<<<< HEAD
         /* no match, fail */
+=======
+>>>>>>> d301c76 (config UPDATE implemented CRL for TLS)
         ERR(session, "Cert verify: fail (%s).", X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509_ctx)));
         return 0;
     }
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 74b5720..26ce69b 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -37,7 +37,7 @@
 
 #append tests depending on SSH/TLS
 if(ENABLE_SSH_TLS)
-    list(APPEND tests test_two_channels test_keystore test_config_new test_truststore test_ec test_ed25519 test_replace test_endpt_share_clients test_tls)
+    list(APPEND tests test_two_channels test_keystore test_config_new test_truststore test_ec test_ed25519 test_replace test_endpt_share_clients test_tls test_crl)
 endif()
 
 foreach(src IN LISTS libsrc)
diff --git a/tests/data/crl.pem b/tests/data/crl.pem
new file mode 100644
index 0000000..0becd57
--- /dev/null
+++ b/tests/data/crl.pem
@@ -0,0 +1,12 @@
+-----BEGIN X509 CRL-----
+MIIB2zCBxAIBATANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJDWjETMBEGA1UE
+CAwKU29tZS1TdGF0ZTENMAsGA1UEBwwEQnJubzEPMA0GA1UECgwGQ0VTTkVUMQww
+CgYDVQQLDANUTUMxETAPBgNVBAMMCHNlcnZlcmNhFw0yMzA2MTUxMTMxMzBaFw0z
+MzA2MTIxMTMxMzBaMBwwGgIJAJXrkmAO99aQFw0yMzA2MTIxNDAzMTJaoA8wDTAL
+BgNVHRQEBAICEAUwDQYJKoZIhvcNAQELBQADggEBAMbxn/kkds6i60o31eQvaI49
+XXQiEnZ+15r8ehkeKv4VOQBeHbddrYuhoaESdVLeB2wzphbLLPAOsUvqHxtM4eO2
+faDDhpquHL3eKsHZA5UyAl9vEHyzONXiSoJvVNvC5dtQHflgUtqKwOnhrvxlUBqV
+pcwVWfqE+opm19f8iNaA4OgFn9dFyaRnpgzWILGOykzW+aEzmCpkVYeUgR1RqBm5
+bgpzQBma2Mg9AjWIeTwGozoANt/Q7GJhdmeZEp41iDc1HElFMUyYspClaYfRKzuE
+VvrHeKOFQIjhQcoPO6oH5QwYgXKUBFuTJD5who3hHbu+6u3upaJ6RZGcwVa+Ly8=
+-----END X509 CRL-----
diff --git a/tests/test_crl.c b/tests/test_crl.c
new file mode 100644
index 0000000..dcf8da8
--- /dev/null
+++ b/tests/test_crl.c
@@ -0,0 +1,223 @@
+/**
+ * @file test_crl.c
+ * @author Roman Janota <janota@cesnet.cz>
+ * @brief libnetconf2 TLS CRL test
+ *
+ * @copyright
+ * Copyright (c) 2023 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#define _GNU_SOURCE
+
+#include <pthread.h>
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cmocka.h>
+
+#include "tests/config.h"
+
+#define NC_ACCEPT_TIMEOUT 2000
+#define NC_PS_POLL_TIMEOUT 2000
+
+struct ly_ctx *ctx;
+
+struct test_state {
+    pthread_barrier_t barrier;
+};
+
+char buffer[512];
+char expected[512];
+
+static void
+test_msg_callback(const struct nc_session *session, NC_VERB_LEVEL level, const char *msg)
+{
+    (void) level;
+    (void) session;
+
+    if (strstr(msg, expected)) {
+        strcpy(buffer, msg);
+    }
+
+    printf("%s\n", msg);
+}
+
+static void *
+server_thread(void *arg)
+{
+    NC_MSG_TYPE msgtype;
+    struct nc_session *session;
+    struct test_state *state = arg;
+
+    /* set print clb so we get access to messages */
+    nc_set_print_clb_session(test_msg_callback);
+    buffer[0] = '\0';
+    strcpy(expected, "revoked per CRL");
+
+    /* accept a session and add it to the poll session structure */
+    pthread_barrier_wait(&state->barrier);
+    msgtype = nc_accept(NC_ACCEPT_TIMEOUT, ctx, &session);
+    assert_int_equal(msgtype, NC_MSG_ERROR);
+
+    assert_int_not_equal(strlen(buffer), 0);
+
+    nc_session_free(session, NULL);
+    return NULL;
+}
+
+static void *
+client_thread(void *arg)
+{
+    int ret;
+    struct nc_session *session = NULL;
+    struct test_state *state = arg;
+
+    ret = nc_client_set_schema_searchpath(MODULES_DIR);
+    assert_int_equal(ret, 0);
+
+    /* set client cert */
+    ret = nc_client_tls_set_cert_key_paths(TESTS_DIR "/data/client.crt", TESTS_DIR "/data/client.key");
+    assert_int_equal(ret, 0);
+
+    /* set client ca */
+    ret = nc_client_tls_set_trusted_ca_paths(NULL, TESTS_DIR "/data");
+    assert_int_equal(ret, 0);
+
+    pthread_barrier_wait(&state->barrier);
+    session = nc_connect_tls("127.0.0.1", 10005, NULL);
+
+    nc_session_free(session, NULL);
+    return NULL;
+}
+
+static void
+test_nc_tls(void **state)
+{
+    int ret, i;
+    pthread_t tids[2];
+
+    assert_non_null(state);
+
+    ret = pthread_create(&tids[0], NULL, client_thread, *state);
+    assert_int_equal(ret, 0);
+    ret = pthread_create(&tids[1], NULL, server_thread, *state);
+    assert_int_equal(ret, 0);
+
+    for (i = 0; i < 2; i++) {
+        pthread_join(tids[i], NULL);
+    }
+}
+
+static int
+setup_f(void **state)
+{
+    int ret;
+    struct lyd_node *tree = NULL;
+    struct test_state *test_state;
+
+    nc_verbosity(NC_VERB_VERBOSE);
+
+    /* init barrier */
+    test_state = malloc(sizeof *test_state);
+    assert_non_null(test_state);
+
+    ret = pthread_barrier_init(&test_state->barrier, NULL, 2);
+    assert_int_equal(ret, 0);
+
+    *state = test_state;
+
+    ret = ly_ctx_new(MODULES_DIR, 0, &ctx);
+    assert_int_equal(ret, 0);
+
+    ret = nc_server_init_ctx(&ctx);
+    assert_int_equal(ret, 0);
+
+    ret = nc_server_config_load_modules(&ctx);
+    assert_int_equal(ret, 0);
+
+    /* create new address and port data */
+    ret = nc_server_config_new_address_port(ctx, "endpt", NC_TI_OPENSSL, "127.0.0.1", "10005", &tree);
+    assert_int_equal(ret, 0);
+
+    /* create new server certificate data */
+    ret = nc_server_config_new_tls_server_certificate(ctx, "endpt", NULL, TESTS_DIR "/data/server.key", TESTS_DIR "/data/server.crt", &tree);
+    assert_int_equal(ret, 0);
+
+    /* create new end entity client cert data */
+    ret = nc_server_config_new_tls_client_certificate(ctx, "endpt", "client_cert", TESTS_DIR "/data/client.crt", &tree);
+    assert_int_equal(ret, 0);
+
+    /* create new client ca data */
+    ret = nc_server_config_new_tls_client_ca(ctx, "endpt", "client_ca", TESTS_DIR "/data/serverca.pem", &tree);
+    assert_int_equal(ret, 0);
+
+    /* create new cert-to-name */
+    ret = nc_server_config_new_tls_ctn(ctx, "endpt", 1,
+            "04:85:6B:75:D1:1A:86:E0:D8:FE:5B:BD:72:F5:73:1D:07:EA:32:BF:09:11:21:6A:6E:23:78:8E:B6:D5:73:C3:2D",
+            NC_TLS_CTN_SPECIFIED, "client", &tree);
+    assert_int_equal(ret, 0);
+
+    /* limit TLS version to 1.3 */
+    ret = nc_server_config_new_tls_version(ctx, "endpt", NC_TLS_VERSION_13, &tree);
+    assert_int_equal(ret, 0);
+
+    /* set the TLS cipher */
+    ret = nc_server_config_new_tls_ciphers(ctx, "endpt", &tree, 3, "tls-aes-128-ccm-sha256", "tls-aes-128-gcm-sha256", "tls-chacha20-poly1305-sha256");
+    assert_int_equal(ret, 0);
+
+    ret = nc_server_config_new_tls_crl_url(ctx, "endpt", "abc", &tree);
+    assert_int_equal(ret, 0);
+
+    ret = nc_server_config_new_tls_crl_path(ctx, "endpt", TESTS_DIR "/data/crl.pem", &tree);
+    assert_int_equal(ret, 0);
+
+    /* configure the server based on the data */
+    ret = nc_server_config_setup_diff(tree);
+    assert_int_equal(ret, 0);
+
+    ret = nc_server_init();
+    assert_int_equal(ret, 0);
+
+    lyd_free_all(tree);
+
+    return 0;
+}
+
+static int
+teardown_f(void **state)
+{
+    int ret = 0;
+    struct test_state *test_state;
+
+    assert_non_null(state);
+    test_state = *state;
+
+    ret = pthread_barrier_destroy(&test_state->barrier);
+    assert_int_equal(ret, 0);
+
+    free(*state);
+    nc_client_destroy();
+    nc_server_destroy();
+    ly_ctx_destroy(ctx);
+
+    return 0;
+}
+
+int
+main(void)
+{
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test_setup_teardown(test_nc_tls, setup_f, teardown_f),
+    };
+
+    setenv("CMOCKA_TEST_ABORT", "1", 1);
+    return cmocka_run_group_tests(tests, NULL, NULL);
+}