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);
+}